File: source/socket-message.js

/**
 * The current version of the internal <u>Signaling Message (SM)</u> Protocol that Skylink is using.<br>
 * - This is not a feature for developers to use but rather for SDK developers to
 *   see the Protocol version used in this Skylink version.
 * - In some cases, this information may be used for reporting issues with Skylink.
 * - SM_PROTOCOL VERSION: <code>0.1.</code>.
 * @attribute SM_PROTOCOL_VERSION
 * @type String
 * @required
 * @component Socket
 * @for Skylink
 * @since 0.6.0
 */
Skylink.prototype.SM_PROTOCOL_VERSION = '0.1.1';

/**
 * The list of Protocol types that is used for messaging using
 *   the platform signaling socket connection.
 * @attribute _SIG_MESSAGE_TYPE
 * @type JSON
 * @param {String} JOIN_ROOM Protocol sent from Skylink to platform signaling to let
 *    self join the room. Join room Step 1.
 * @param {String} IN_ROOM Protocol received from platform signaling to inform
 *    Skylink that self has joined the room. Join room Step 2 (Completed).
 * @param {String} ENTER Protocol Skylink sends to all Peer peers
 *    in the room to start handshake connection. Handshake connection Step 1.
 * @param {String} WELCOME Protocol received to Peer as a response
 *    to self <code>ENTER</code> message. This is sent as a response to
 *    Peer <code>ENTER</code> message. Handshake connection Step 2.
 * @param {String} OFFER Protocol sent to Peer as a response
 *    to the <code>WELCOME</code> message received after generating the offer session description with
 *    <code>RTCPeerConnection.createOffer()</code>. This is received as a response from
 *    Peer after sending <code>WELCOME</code> message and requires
 *    setting into the Peer connection before sending the
 *    <code>ANSWER</code> response. Handshake connection Step 3.
 * @param {String} ANSWER Protocol received from Peer as a response
 *    to self <code>OFFER</code> message offer session description and requires setting
 *    into the Peer connection. This is sent to Peer as a response
 *    to the <code>OFFER</code> message received after setting the received <code>OFFER</code>
 *    message and generating the answer session description with <code>RTCPeerConnection.createAnswer()</code>.
 *    Handshake connection Step 4 (Completed).
 * @param {String} CANDIDATE Protocol received from Peer when connection
 *    ICE candidate has been generated and requires self to add to the Peer connection.
 * @param {String} BYE Protocol received from platform signaling when a Peer has left
 *    the room.
 * @param {String} REDIRECT Protocol received from platform signaling when self is kicked out from
 *    the currently joined room.
 * @param {String} UPDATE_USER Protocol received when a Peer information has been
 *    updated. The message object should contain the updated peer information.
 *    This is broadcasted by self when self peer information is updated.
 * @param {String} ROOM_LOCK Protocol received when the current joined room lock status have
 *    been updated. The message object should contain the updated room lock status.
 *    This is broadcasted by self when self updates the room lock status.
 * @param {String} MUTE_VIDEO Protocol received when a Peer Stream video media streaming
 *    muted status have been updated. The message object should contain the updated Stream video media
 *    streaming muted status. This is broadcasted by self when self Stream video media streaming
 *    muted status have been updated.
 * @param {String} MUTE_AUDIO Protocol received when a Peer connection Stream audio media streaming
 *    muted status have been updated. The message object should contain the updated Stream audio media
 *    streaming muted status. This is broadcasted by self when self Stream audio media streaming
 *    muted status have been updated.
 * @param {String} PUBLIC_MESSAGE Protocol received when a Peer broadcasts a message
 *    object to all Peer peers via the platform signaling socket connection.
 *    This is broadcasted by self when self sends the message object.
 * @param {String} PRIVATE_MESSAGE Protocol received when a Peer sends a message
 *    object targeted to several Peer peers via the platform signaling socket connection.
 *    This is sent by self when self sends the message object.
 * @param {String} RESTART Protocol received when a Peer connection requires a reconnection.
 *    At this point, the Peer connection have to recreate the <code>RTCPeerConnection</code>
 *    object again. This is sent by self when self initiates the reconnection.
 * @param {String} STREAM Protocol received when a Peer connection Stream status have
 *    changed.
 * @param {String} GET_PEERS Protocol for privileged self to get the list of Peer peers
 *    under the same parent Application Key.
 * @param {String} PEER_LIST Protocol to retrieve a list of peers under the same parent.
 * @param {String} INTRODUCE Protocol sent to the platform signaling to
 *    introduce Peer peers to each other.
 * @param {String} INTRODUCE_ERROR Protocol received when Peer peers introduction failed.
 * @param {String} APPROACH Protocol to indicate that a Peer has been introduced.
 *    At this point, self would send an <code>ENTER</code> to introduced Peer
 *    to start the handshake connection.
 * @param {String} GROUP Protocol received that bundles messages together when socket messages are
 *    sent less than 1 second interval apart from the previous sent socket message. This would
 *    prevent receiving <code>REDIRECT</code> from the platform signaling.
 * @readOnly
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.6
 */
Skylink.prototype._SIG_MESSAGE_TYPE = {
  JOIN_ROOM: 'joinRoom',
  IN_ROOM: 'inRoom',
  ENTER: 'enter',
  WELCOME: 'welcome',
  RESTART: 'restart',
  OFFER: 'offer',
  ANSWER: 'answer',
  CANDIDATE: 'candidate',
  BYE: 'bye',
  REDIRECT: 'redirect',
  UPDATE_USER: 'updateUserEvent',
  ROOM_LOCK: 'roomLockEvent',
  MUTE_VIDEO: 'muteVideoEvent',
  MUTE_AUDIO: 'muteAudioEvent',
  PUBLIC_MESSAGE: 'public',
  PRIVATE_MESSAGE: 'private',
  STREAM: 'stream',
  GROUP: 'group',
  GET_PEERS: 'getPeers',
  PEER_LIST: 'peerList',
  INTRODUCE: 'introduce',
  INTRODUCE_ERROR: 'introduceError',
  APPROACH: 'approach'
};

/**
 * The flag that indicates if MCU is enabled.
 * @attribute _hasMCU
 * @type Boolean
 * @development true
 * @private
 * @component MCU
 * @for Skylink
 * @since 0.5.4
 */
Skylink.prototype._hasMCU = false;

/**
 * Stores the list of types of socket messages that requires to be queued or bundled
 *    before sending to the server to prevent platform signaling from dropping of socket messages.
 * @attribute _groupMessageList
 * @type Array
 * @private
 * @required
 * @component Message
 * @for Skylink
 * @since 0.5.10
 */
Skylink.prototype._groupMessageList = [
  Skylink.prototype._SIG_MESSAGE_TYPE.STREAM,
  Skylink.prototype._SIG_MESSAGE_TYPE.UPDATE_USER,
  Skylink.prototype._SIG_MESSAGE_TYPE.ROOM_LOCK,
  Skylink.prototype._SIG_MESSAGE_TYPE.MUTE_AUDIO,
  Skylink.prototype._SIG_MESSAGE_TYPE.MUTE_VIDEO,
  Skylink.prototype._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
];

/**
 * The flag that indicates if MCU is in the room and is enabled.
 * @attribute _hasMCU
 * @type Boolean
 * @development true
 * @default false
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.4
 */
Skylink.prototype._hasMCU = false;


/**
 * The flag that indicates that the current self connection
 *   should only receive streaming Stream objects from other Peer connection
 *   and not send streaming Stream objects to other Peer connection.
 * @attribute _receiveOnly
 * @type Boolean
 * @default false
 * @private
 * @required
 * @component Message
 * @for Skylink
 * @since 0.5.10
 */
 Skylink.prototype._receiveOnly = false;


/**
 * Parses any <code>GROUP</code> type of message received and split them up to
 *   send them to {{#crossLink "Skylink/_processSingleMessage:method"}}_processSingleMessage(){{/crossLink}}
 *   to handle the individual message object received.
 * If the message is not <code>GROUP</code> type of message received, it will send
 *   it directly to {{#crossLink "Skylink/_processSingleMessage:method"}}_processSingleMessage(){{/crossLink}}
 * @method _processSigMessage
 * @param {String} messageString The message object in JSON string.
 * @private
 * @component Message
 * @for Skylink
 * @since 0.1.0
 */
Skylink.prototype._processSigMessage = function(messageString) {
  var message = JSON.parse(messageString);
  if (message.type === this._SIG_MESSAGE_TYPE.GROUP) {
    log.debug('Bundle of ' + message.lists.length + ' messages');
    for (var i = 0; i < message.lists.length; i++) {
      this._processSingleMessage(JSON.parse(message.lists[i]));
    }
  } else {
    this._processSingleMessage(message);
  }
};

/**
 * Routes the data received to the relevant Protocol handler based on the socket message received.
 * @method _processingSingleMessage
 * @param {JSON} message The message object received.
 * @private
 * @component Message
 * @for Skylink
 * @since 0.1.0
 */
Skylink.prototype._processSingleMessage = function(message) {
  this._trigger('channelMessage', message);
  var origin = message.mid;
  if (!origin || origin === this._user.sid) {
    origin = 'Server';
  }
  log.debug([origin, null, null, 'Received from peer ->'], message.type);
  if (message.mid === this._user.sid &&
    message.type !== this._SIG_MESSAGE_TYPE.REDIRECT &&
    message.type !== this._SIG_MESSAGE_TYPE.IN_ROOM) {
    log.debug([origin, null, null, 'Ignoring message ->'], message.type);
    return;
  }
  switch (message.type) {
  //--- BASIC API Messages ----
  case this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE:
    this._publicMessageHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE:
    this._privateMessageHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.IN_ROOM:
    this._inRoomHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.ENTER:
    this._enterHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.WELCOME:
    this._welcomeHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.RESTART:
    this._restartHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.OFFER:
    this._offerHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.ANSWER:
    this._answerHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.CANDIDATE:
    this._candidateHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.BYE:
    this._byeHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.REDIRECT:
    this._redirectHandler(message);
    break;
    //--- ADVANCED API Messages ----
  case this._SIG_MESSAGE_TYPE.UPDATE_USER:
    this._updateUserEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.MUTE_VIDEO:
    this._muteVideoEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.MUTE_AUDIO:
    this._muteAudioEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.STREAM:
    this._streamEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.ROOM_LOCK:
    this._roomLockEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.PEER_LIST:
    this._peerListEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.INTRODUCE_ERROR:
    this._introduceErrorEventHandler(message);
    break;
  case this._SIG_MESSAGE_TYPE.APPROACH:
    this._approachEventHandler(message);
    break;
  default:
    log.error([message.mid, null, null, 'Unsupported message ->'], message.type);
    break;
  }
};

/**
 * Handles the PEER_LIST message event received from the platform signaling.
 * @method _peerListEventHandler
 * @param {String} message.type Protocol step: <code>"peerList"</code>.
 * @param {JSON} message The message object received from platform signaling.
 * @param {String} message.type Protocol step: <code>"peerList"</code>.
 * @param {Object} message.result The received resulting object
 *   <small>E.g. <code>{room1: [peer1, peer2], room2: ...}</code></small>.
 * @private
 * @component Message
 * @for Skylink
 * @since 0.6.1
 */
Skylink.prototype._peerListEventHandler = function(message){
  var self = this;
  self._peerList = message.result;
  log.log(['Server', null, message.type, 'Received list of peers'], self._peerList);
  self._trigger('getPeersStateChange',self.GET_PEERS_STATE.RECEIVED, self._user.sid, self._peerList);
};

/**
 * Handles the INTRODUCE_ERROR message event received from the platform signaling.
 * @method _introduceErrorEventHandler
 * @param {JSON} message The message object received from platform signaling.
 * @param {String} message.type Protocol step <code>"introduceError"</code>.
 * @param {Object} message.reason The error message.
 * @param {Object} message.sendingPeerId Id of the peer initiating the handshake
 * @param {Object} message.receivingPeerId Id of the peer receiving the handshake
 * @private
 * @component Message
 * @for Skylink
 * @since 0.6.1
 */
Skylink.prototype._introduceErrorEventHandler = function(message){
  var self = this;
  log.log(['Server', null, message.type, 'Introduce failed. Reason: '+message.reason]);
  self._trigger('introduceStateChange',self.INTRODUCE_STATE.ERROR, self._user.sid,
    message.sendingPeerId, message.receivingPeerId, message.reason);
};

/**
 * Handles the APPROACH message event received from the platform signaling.
 * @method _approachEventHandler
 * @param {JSON} message The message object received from platform signaling.
 * @param {String} message.type Protocol step <code>"approach"</code>.
 * @param {Object} message.target The peer to initiate the handshake to
 * @private
 * @component Message
 * @for Skylink
 * @since 0.6.1
 */
Skylink.prototype._approachEventHandler = function(message){
  var self = this;
  log.log(['Server', null, message.type, 'Approaching peer'], message.target);
  // self._room.connection.peerConfig = self._setIceServers(message.pc_config);
  // self._inRoom = true;
  self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  self._sendChannelMessage({
    type: self._SIG_MESSAGE_TYPE.ENTER,
    mid: self._user.sid,
    rid: self._room.id,
    agent: window.webrtcDetectedBrowser,
    version: window.webrtcDetectedVersion,
    os: window.navigator.platform,
    userInfo: self.getPeerInfo(),
    receiveOnly: self._receiveOnly,
    sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
    target: message.target,
    weight: self._peerPriorityWeight
  });
};

/**
 * Handles the REDIRECT message event received from the platform signaling.
 * @method _redirectHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>REDIRECT</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.info The message received from the platform signaling when
 *   the system action and reason is given.
 * @param {String} message.action The system action that is received from the platform signaling.
 *   [Rel: Skylink.SYSTEM_ACTION]
 * @param {String} message.reason The reason received from the platform signaling behind the
 *   system action given. [Rel: Skylink.SYSTEM_ACTION_REASON]
 * @param {String} message.type Protocol step <code>"redirect"</code>.
 * @trigger systemAction
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.1
 */
Skylink.prototype._redirectHandler = function(message) {
  log.log(['Server', null, message.type, 'System action warning:'], {
    message: message.info,
    reason: message.reason,
    action: message.action
  });

  if (message.action === this.SYSTEM_ACTION.REJECT) {
  	for (var key in this._peerConnections) {
  		if (this._peerConnections.hasOwnProperty(key)) {
  			this._removePeer(key);
  		}
  	}
  }
  this._trigger('systemAction', message.action, message.info, message.reason);
};

/**
 * Handles the UPDATE_USER Protocol message event received from the platform signaling.
 * @method _updateUserEventHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>UPDATE_USER</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {JSON|String} message.userData The updated Peer information
 *    custom user data.
 * @param {String} message.type Protocol step <code>"updateUserEvent"</code>.
 * @trigger peerUpdated
 * @private
 * @component Message
 * @for Skylink
 * @since 0.2.0
 */
Skylink.prototype._updateUserEventHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type, 'Peer updated userData:'], message.userData);
  if (this._peerInformations[targetMid]) {
    this._peerInformations[targetMid].userData = message.userData || {};
    this._trigger('peerUpdated', targetMid,
      this.getPeerInfo(targetMid), false);
  } else {
    log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  }
};

/**
 * Handles the ROOM_LOCK Protocol message event received from the platform signaling.
 * @method _roomLockEventHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>ROOM_LOCK</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.lock The flag that indicates if the currently joined room is locked.
 * @param {String} message.type Protocol step <code>"roomLockEvent"</code>.
 * @trigger roomLock
 * @private
 * @component Message
 * @for Skylink
 * @since 0.2.0
 */
Skylink.prototype._roomLockEventHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, message.type, 'Room lock status:'], message.lock);
  this._trigger('roomLock', message.lock, targetMid,
    this.getPeerInfo(targetMid), false);
};

/**
 * Handles the MUTE_AUDIO Protocol message event received from the platform signaling.
 * @method _muteAudioEventHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>MUTE_AUDIO</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {Boolean} message.muted The flag that
 *   indicates if the remote Stream object audio streaming is muted.
 * @param {String} message.type Protocol step <code>"muteAudioEvent"</code>.
 * @trigger peerUpdated
 * @private
 * @component Message
 * @for Skylink
 * @since 0.2.0
 */
Skylink.prototype._muteAudioEventHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type, 'Peer\'s audio muted:'], message.muted);
  if (this._peerInformations[targetMid]) {
    this._peerInformations[targetMid].mediaStatus.audioMuted = message.muted;
    this._trigger('peerUpdated', targetMid,
      this.getPeerInfo(targetMid), false);
  } else {
    log.log([targetMid, message.type, 'Peer does not have any user information']);
  }
};

/**
 * Handles the MUTE_VIDEO Protocol message event received from the platform signaling.
 * @method _muteVideoEventHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>MUTE_VIDEO</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.muted The flag that
 *   indicates if the remote Stream object video streaming is muted.
 * @param {String} message.type Protocol step <code>"muteVideoEvent"</code>.
 * @trigger peerUpdated
 * @private
 * @component Message
 * @for Skylink
 * @since 0.2.0
 */
Skylink.prototype._muteVideoEventHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type, 'Peer\'s video muted:'], message.muted);
  if (this._peerInformations[targetMid]) {
    this._peerInformations[targetMid].mediaStatus.videoMuted = message.muted;
    this._trigger('peerUpdated', targetMid,
      this.getPeerInfo(targetMid), false);
  } else {
    log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  }
};

/**
 * Handles the STREAM Protocol message event received from the platform signaling.
 * @method _streamEventHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>STREAM</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.status The Peer connection remote Stream streaming current status.
 * <ul>
 * <li><code>ended</code>: The Peer connection remote Stream streaming has ended</li>
 * </ul>
 * @param {String} message.cid The Skylink server connection key for the selected room.
 * @param {String} message.sessionType The Peer connection remote Stream streaming
 *   session type. If value is <code>"stream"</code>, the Stream streaming session
 *   is normal user media streaming, else if it is <code>"screensharing"</code>, the
 *   Stream streaming session is screensharing session.
 * @param {String} message.type Protocol step <code>"stream"</code>.
 * @trigger streamEnded
 * @private
 * @component Message
 * @for Skylink
 * @since 0.2.0
 */
Skylink.prototype._streamEventHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type, 'Peer\'s stream status:'], message.status);

  if (this._peerInformations[targetMid]) {

  	if (message.status === 'ended') {
  		this._trigger('streamEnded', targetMid, this.getPeerInfo(targetMid),
        false, message.sessionType === 'screensharing');

      if (this._peerConnections[targetMid]) {
        this._peerConnections[targetMid].hasStream = false;
        if (message.sessionType === 'screensharing') {
          this._peerConnections[targetMid].hasScreen = false;
        }
      } else {
        log.log([targetMid, null, message.type, 'Peer connection not found']);
      }
  	}

  } else {
    log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  }
};

/**
 * Handles the BYE Protocol message event received from the platform signaling.
 * @method _byeHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>BYE</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.type Protocol step <code>"bye"</code>.
 * @trigger peerLeft
 * @private
 * @component Message
 * @for Skylink
 * @since 0.1.0
 */
Skylink.prototype._byeHandler = function(message) {
  var targetMid = message.mid;
  var selfId = (this._user || {}).sid;

  if (selfId !== targetMid){
    log.log([targetMid, null, message.type, 'Peer has left the room']);
    this._removePeer(targetMid);
  } else {
    log.log([targetMid, null, message.type, 'Self has left the room']);
  }
};

/**
 * Handles the PRIVATE_MESSAGE Protocol message event received from the platform signaling.
 * @method _privateMessageHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>PRIVATE_MESSAGE</code> payload.
 * @param {JSON|String} message.data The Message object.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.cid The Skylink server connection key for the selected room.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {String} message.type Protocol step: <code>"private"</code>.
 * @trigger incomingMessage
 * @private
 * @component Message
 * @for Skylink
 * @since 0.4.0
 */
Skylink.prototype._privateMessageHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type,
    'Received private message from peer:'], message.data);
  this._trigger('incomingMessage', {
    content: message.data,
    isPrivate: true,
    targetPeerId: message.target, // is not null if there's user
    isDataChannel: false,
    senderPeerId: targetMid
  }, targetMid, this.getPeerInfo(targetMid), false);
};

/**
 * Handles the PUBLIC_MESSAGE Protocol message event received from the platform signaling.
 * @method _publicMessageHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>PUBLIC_MESSAGE</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.cid The Skylink server connection key for the selected room.
 * @param {String} message.muted The flag to indicate if the User's audio
 *    stream is muted or not.
 * @param {String} message.type Protocol step: <code>"public"</code>.
 * @trigger incomingMessage
 * @private
 * @component Message
 * @for Skylink
 * @since 0.4.0
 */
Skylink.prototype._publicMessageHandler = function(message) {
  var targetMid = message.mid;
  log.log([targetMid, null, message.type,
    'Received public message from peer:'], message.data);
  this._trigger('incomingMessage', {
    content: message.data,
    isPrivate: false,
    targetPeerId: null, // is not null if there's user
    isDataChannel: false,
    senderPeerId: targetMid
  }, targetMid, this.getPeerInfo(targetMid), false);
};

/**
 * Handles the IN_ROOM Protocol message event received from the platform signaling.
 * @method _inRoomHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>IN_ROOM</code> payload.
 * @param {JSON} message Expected IN_ROOM data object format.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.sid The self session socket connection ID. This
 *   is used by the signalling socket connection as ID to target
 *   self and the peers Peer ID.
 * @param {JSON} message.pc_config The Peer connection iceServers configuration.
 * @param {String} message.type Protocol step: <code>"inRoom"</code>.
 * @trigger peerJoined
 * @private
 * @component Message
 * @for Skylink
 * @since 0.1.0
 */
Skylink.prototype._inRoomHandler = function(message) {
  var self = this;
  log.log(['Server', null, message.type, 'User is now in the room and ' +
    'functionalities are now available. Config received:'], message.pc_config);
  self._room.connection.peerConfig = self._setIceServers(message.pc_config);
  self._inRoom = true;
  self._user.sid = message.sid;
  self._peerPriorityWeight = (new Date()).getTime();

  self._trigger('peerJoined', self._user.sid, self.getPeerInfo(), true);
  self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);

  if (typeof message.tieBreaker === 'number') {
    self._peerPriorityWeight = message.tieBreaker;
  }

  // Make Firefox the answerer always when connecting with other browsers
  if (window.webrtcDetectedBrowser === 'firefox') {
    log.warn('Decreasing weight for Firefox browser connection');

    self._peerPriorityWeight -= 100000000000;
  }

  if (self._mediaScreen && self._mediaScreen !== null) {
    self._trigger('incomingStream', self._user.sid, self._mediaScreen, true, self.getPeerInfo());
  } else if (self._mediaStream && self._mediaStream !== null) {
    self._trigger('incomingStream', self._user.sid, self._mediaStream, true, self.getPeerInfo());
  }
  // NOTE ALEX: should we wait for local streams?
  // or just go with what we have (if no stream, then one way?)
  // do we hardcode the logic here, or give the flexibility?
  // It would be better to separate, do we could choose with whom
  // we want to communicate, instead of connecting automatically to all.
  self._sendChannelMessage({
    type: self._SIG_MESSAGE_TYPE.ENTER,
    mid: self._user.sid,
    rid: self._room.id,
    agent: window.webrtcDetectedBrowser,
    version: window.webrtcDetectedVersion,
    os: window.navigator.platform,
    userInfo: self.getPeerInfo(),
    receiveOnly: self._receiveOnly,
    sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
    weight: self._peerPriorityWeight
  });
};

/**
 * Handles the ENTER Protocol message event received from the platform signaling.
 * @method _enterHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>ENTER</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {Boolean} [message.receiveOnly=false] The flag that indicates if the Peer
 *   connection would send Stream or not (receive only).
 * @param {JSON} message.userInfo The peer information associated
 *   with the Peer Connection.
 * @param {String|JSON} message.userInfo.userData The custom user data
 *   information set by developer. This custom user data can also
 *   be set in <a href="#method_setUserData">setUserData()</a>.
 * @param {JSON} message.userInfo.settings The Peer Stream
 *   streaming settings information. If both audio and video
 *   option is <code>false</code>, there should be no
 *   receiving remote Stream object from this associated Peer.
 * @param {Boolean|JSON} [message.userInfo.settings.audio=false] The
 *   Peer Stream streaming audio settings. If
 *   <code>false</code>, it means that audio streaming is disabled in
 *   the remote Stream of the Peer.
 * @param {Boolean} [message.userInfo.settings.audio.stereo] The flag that indicates if
 *   stereo should be enabled in the Peer connection Stream
 *   audio streaming.
 * @param {Boolean|JSON} [message.userInfo.settings.video=false] The Peer
 *   Stream streaming video settings. If <code>false</code>, it means that
 *   video streaming is disabled in the remote Stream of the Peer.
 * @param {JSON} [message.userInfo.settings.video.resolution] The Peer
 *   Stream streaming video resolution settings. Setting the resolution may
 *   not force set the resolution provided as it depends on the how the
 *   browser handles the resolution. [Rel: Skylink.VIDEO_RESOLUTION]
 * @param {Number} [message.userInfo.settings.video.resolution.width] The Peer
 *   Stream streaming video resolution width.
 * @param {Number} [message.userInfo.settings.video.resolution.height] The Peer
 *   Stream streaming video resolution height.
 * @param {Number} [message.userInfo.settings.video.frameRate] The Peer
 *   Stream streaming video maximum frameRate.
 * @param {Boolean} [message.userInfo.settings.video.screenshare=false] The flag
 *   that indicates if the Peer connection Stream object sent
 *   is a screensharing stream or not.
 * @param {String} [message.userInfo.settings.bandwidth] The Peer configuration for
 *   the maximum sending bandwidth. The flags set may or may not work depending
 *   on the browser implementations and how it handles it.
 * @param {String} [message.userInfo.settings.bandwidth.audio] The maximum
 *   sending audio bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the audio bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.video] The maximum
 *   sending video bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the video bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.data] The maximum
 *   sending data bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the data bitrate to the browser defaults.
 * @param {JSON} message.userInfo.mediaStatus The Peer Stream mute
 *   settings for both audio and video streamings.
 * @param {Boolean} [message.userInfo.mediaStatus.audioMuted=true] The flag that
 *   indicates if the remote Stream object audio streaming is muted. If
 *   there is no audio streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {Boolean} [message.userInfo.mediaStatus.videoMuted=true] The flag that
 *   indicates if the remote Stream object video streaming is muted. If
 *   there is no video streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {String} message.agent.name The Peer platform browser or agent name.
 * @param {Number} message.version The Peer platform browser or agent version.
 * @param {Number} message.os The Peer platform name.
 * @param {String} message.sessionType The Peer connection remote Stream streaming
 *   session type. If value is <code>"stream"</code>, the Stream streaming session
 *   is normal user media streaming, else if it is <code>"screensharing"</code>, the
 *   Stream streaming session is screensharing session.
 * @param {String} message.type Protocol step <code>"enter"</code>.
 * @trigger handshakeProgress, peerJoined
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.1
 */
Skylink.prototype._enterHandler = function(message) {
  var self = this;
  var targetMid = message.mid;
  log.log([targetMid, null, message.type, 'Incoming peer have initiated ' +
    'handshake. Peer\'s information:'], message.userInfo);
  // need to check entered user is new or not.
  // peerInformations because it takes a sequence before creating the
  // peerconnection object. peerInformations are stored at the start of the
  // handshake, so user knows if there is a peer already.
  if (self._peerInformations[targetMid]) {
    // NOTE ALEX: and if we already have a connection when the peer enter,
    // what should we do? what are the possible use case?
    log.log([targetMid, null, message.type, 'Ignoring message as peer is already added']);
    return;
  }
  // add peer
  self._addPeer(targetMid, {
    agent: message.agent,
    version: message.version,
    os: message.os
  }, false, false, message.receiveOnly, message.sessionType === 'screensharing');
  self._peerInformations[targetMid] = message.userInfo || {};
  self._peerInformations[targetMid].agent = {
    name: message.agent,
    version: message.version,
    os: message.os || ''
  };
  if (targetMid !== 'MCU') {
    self._trigger('peerJoined', targetMid, message.userInfo, false);

  } else {
    log.info([targetMid, 'RTCPeerConnection', 'MCU', 'MCU feature has been enabled'], message);
    log.log([targetMid, null, message.type, 'MCU has joined'], message.userInfo);
    this._hasMCU = true;
    this._trigger('serverPeerJoined', targetMid, this.SERVER_PEER_TYPE.MCU);
  }

  self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);

  self._sendChannelMessage({
    type: self._SIG_MESSAGE_TYPE.WELCOME,
    mid: self._user.sid,
    rid: self._room.id,
    receiveOnly: self._peerConnections[targetMid] ?
    	!!self._peerConnections[targetMid].receiveOnly : false,
    enableIceTrickle: self._enableIceTrickle,
    enableDataChannel: self._enableDataChannel,
    agent: window.webrtcDetectedBrowser,
    version: window.webrtcDetectedVersion,
    os: window.navigator.platform,
    userInfo: self.getPeerInfo(),
    target: targetMid,
    weight: self._peerPriorityWeight,
    sessionType: !!self._mediaScreen ? 'screensharing' : 'stream'
  });

  self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
};

/**
 * Handles the RESTART Protocol message event received from the platform signaling.
 * @method _restartHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>RESTART</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {Boolean} [message.receiveOnly=false] The flag that indicates if the Peer
 *   connection would send Stream or not (receive only).
 * @param {Boolean} [message.enableIceTrickle=false] The flag that indicates
 *    if PeerConnections should enable trickling of ICE to connect the ICE connection.
 * @param {Boolean} [message.enableDataChannel=false] The flag that indicates if
 *   Peer connection should have any DataChannel connections.
 * @param {JSON} message.userInfo The peer information associated
 *   with the Peer Connection.
 * @param {String|JSON} message.userInfo.userData The custom user data
 *   information set by developer. This custom user data can also
 *   be set in <a href="#method_setUserData">setUserData()</a>.
 * @param {JSON} message.userInfo.settings The Peer Stream
 *   streaming settings information. If both audio and video
 *   option is <code>false</code>, there should be no
 *   receiving remote Stream object from this associated Peer.
 * @param {Boolean|JSON} [message.userInfo.settings.audio=false] The
 *   Peer Stream streaming audio settings. If
 *   <code>false</code>, it means that audio streaming is disabled in
 *   the remote Stream of the Peer.
 * @param {Boolean} [message.userInfo.settings.audio.stereo] The flag that indicates if
 *   stereo should be enabled in the Peer connection Stream
 *   audio streaming.
 * @param {Boolean|JSON} [message.userInfo.settings.video=false] The Peer
 *   Stream streaming video settings. If <code>false</code>, it means that
 *   video streaming is disabled in the remote Stream of the Peer.
 * @param {JSON} [message.userInfo.settings.video.resolution] The Peer
 *   Stream streaming video resolution settings. Setting the resolution may
 *   not force set the resolution provided as it depends on the how the
 *   browser handles the resolution. [Rel: Skylink.VIDEO_RESOLUTION]
 * @param {Number} [message.userInfo.settings.video.resolution.width] The Peer
 *   Stream streaming video resolution width.
 * @param {Number} [message.userInfo.settings.video.resolution.height] The Peer
 *   Stream streaming video resolution height.
 * @param {Number} [message.userInfo.settings.video.frameRate] The Peer
 *   Stream streaming video maximum frameRate.
 * @param {Boolean} [message.userInfo.settings.video.screenshare=false] The flag
 *   that indicates if the Peer connection Stream object sent
 *   is a screensharing stream or not.
 * @param {String} [message.userInfo.settings.bandwidth] The Peer configuration for
 *   the maximum sending bandwidth. The flags set may or may not work depending
 *   on the browser implementations and how it handles it.
 * @param {String} [message.userInfo.settings.bandwidth.audio] The maximum
 *   sending audio bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the audio bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.video] The maximum
 *   sending video bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the video bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.data] The maximum
 *   sending data bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the data bitrate to the browser defaults.
 * @param {JSON} message.userInfo.mediaStatus The Peer Stream mute
 *   settings for both audio and video streamings.
 * @param {Boolean} [message.userInfo.mediaStatus.audioMuted=true] The flag that
 *   indicates if the remote Stream object audio streaming is muted. If
 *   there is no audio streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {Boolean} [message.userInfo.mediaStatus.videoMuted=true] The flag that
 *   indicates if the remote Stream object video streaming is muted. If
 *   there is no video streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {String} message.agent.name The Peer platform browser or agent name.
 * @param {Number} message.version The Peer platform browser or agent version.
 * @param {Number} message.os The Peer platform name.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {Number} message.weight The generated handshake reconnection
 *   weight for associated Peer.
 * @param {Number} message.lastRestart The datetime stamp generated using
 *   [Date.now()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
 *   (in ms) used to throttle the Peer reconnection functionality
 *   to prevent less Peer reconnection handshaking errors.
 * @param {Boolean} message.isConnectionRestart The flag that indicates whether the restarting action
 *   is caused by ICE connection or handshake connection failure. Currently, this feature works the same as
 *   <code>message.explict</code> parameter.
 * @param {Boolean} message.explict The flag that indicates whether the restart functionality
 *   is invoked by the application or by Skylink when the ICE connection fails to establish
 *   a "healthy" connection state. Currently, this feature works the same as
 *   <code>message.isConnectionRestart</code> parameter.
 * @param {String} message.sessionType The Peer connection remote Stream streaming
 *   session type. If value is <code>"stream"</code>, the Stream streaming session
 *   is normal user media streaming, else if it is <code>"screensharing"</code>, the
 *   Stream streaming session is screensharing session.
 * @param {String} message.type Protocol step <code>"restart"</code>.
 * @trigger handshakeProgress, peerRestart
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.6
 */
Skylink.prototype._restartHandler = function(message){
  var self = this;
  var targetMid = message.mid;

  if (!self._peerInformations[targetMid]) {
    log.error([targetMid, null, null, 'Peer does not have an existing ' +
      'session. Ignoring restart process.']);
    return;
  }

  // NOTE: for now we ignore, but we should take-note to implement in the near future
  if (self._hasMCU) {
    self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false);
    return;
  }

  self.lastRestart = message.lastRestart || Date.now() || function() { return +new Date(); };

  if (!self._peerConnections[targetMid]) {
    log.error([targetMid, null, null, 'Peer does not have an existing ' +
      'connection. Unable to restart']);
    return;
  }

  // mcu has re-joined
  // NOTE: logic trip since _hasMCU flags are ignored, this could result in failure perhaps?
  if (targetMid === 'MCU') {
    log.log([targetMid, null, message.type, 'MCU has restarted its connection']);
    self._hasMCU = true;
  }

  // Uncomment because we do not need this
  //self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);

  message.agent = (!message.agent) ? 'chrome' : message.agent;
  /*self._enableIceTrickle = (typeof message.enableIceTrickle === 'boolean') ?
    message.enableIceTrickle : self._enableIceTrickle;
  self._enableDataChannel = (typeof message.enableDataChannel === 'boolean') ?
    message.enableDataChannel : self._enableDataChannel;*/

  // re-add information
  self._peerInformations[targetMid] = message.userInfo || {};
  self._peerInformations[targetMid].agent = {
    name: message.agent,
    version: message.version,
    os: message.os || ''
  };

  var agent = (self.getPeerInfo(targetMid) || {}).agent || {};

  // This variable is not used
  //var peerConnectionStateStable = false;

  log.info([targetMid, 'RTCPeerConnection', null, 'Received restart request from peer'], message);
  // we are no longer adding any peer
  /*self._addPeer(targetMid, {
    agent: message.agent,
    version: message.version,
    os: message.os
  }, true, true, message.receiveOnly, message.sessionType === 'screensharing');*/

  // Make peer with highest weight do the offer
  if (self._peerPriorityWeight > message.weight) {
    log.debug([targetMid, 'RTCPeerConnection', null, 'Restarting negotiation'], agent);
    self._doOffer(targetMid, {
      agent: agent.name,
      version: agent.version,
      os: agent.os
    }, true);

  } else {
    log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start re-negotiation'], agent);
    self._sendChannelMessage({
      type: self._SIG_MESSAGE_TYPE.RESTART,
      mid: self._user.sid,
      rid: self._room.id,
      agent: window.webrtcDetectedBrowser,
      version: window.webrtcDetectedVersion,
      os: window.navigator.platform,
      userInfo: self.getPeerInfo(),
      target: targetMid,
      weight: self._peerPriorityWeight,
      enableIceTrickle: self._enableIceTrickle,
      enableDataChannel: self._enableDataChannel,
      receiveOnly: self._peerConnections[targetMid] && self._peerConnections[targetMid].receiveOnly,
      sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
      // SkylinkJS parameters (copy the parameters from received message parameters)
      isConnectionRestart: !!message.isConnectionRestart,
      lastRestart: message.lastRestart,
      explicit: !!message.explicit
    });
  }

  self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false);

  // following the previous logic to do checker always
  self._startPeerConnectionHealthCheck(targetMid, false);
};

/**
 * Handles the WELCOME Protocol message event received from the platform signaling.
 * @method _welcomeHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>WELCOME</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {Boolean} [message.receiveOnly=false] The flag that indicates if the Peer
 *   connection would send Stream or not (receive only).
 * @param {Boolean} [message.enableIceTrickle=false] The flag that indicates
 *    if PeerConnections should enable trickling of ICE to connect the ICE connection.
 * @param {Boolean} [message.enableDataChannel=false] The flag that indicates if
 *   Peer connection should have any DataChannel connections.
 * @param {String|JSON} message.userInfo.userData The custom user data
 *   information set by developer. This custom user data can also
 *   be set in <a href="#method_setUserData">setUserData()</a>.
 * @param {JSON} message.userInfo.settings The Peer Stream
 *   streaming settings information. If both audio and video
 *   option is <code>false</code>, there should be no
 *   receiving remote Stream object from this associated Peer.
 * @param {Boolean|JSON} [message.userInfo.settings.audio=false] The
 *   Peer Stream streaming audio settings. If
 *   <code>false</code>, it means that audio streaming is disabled in
 *   the remote Stream of the Peer.
 * @param {Boolean} [message.userInfo.settings.audio.stereo] The flag that indicates if
 *   stereo should be enabled in the Peer connection Stream
 *   audio streaming.
 * @param {Boolean|JSON} [message.userInfo.settings.video=false] The Peer
 *   Stream streaming video settings. If <code>false</code>, it means that
 *   video streaming is disabled in the remote Stream of the Peer.
 * @param {JSON} [message.userInfo.settings.video.resolution] The Peer
 *   Stream streaming video resolution settings. Setting the resolution may
 *   not force set the resolution provided as it depends on the how the
 *   browser handles the resolution. [Rel: Skylink.VIDEO_RESOLUTION]
 * @param {Number} [message.userInfo.settings.video.resolution.width] The Peer
 *   Stream streaming video resolution width.
 * @param {Number} [message.userInfo.settings.video.resolution.height] The Peer
 *   Stream streaming video resolution height.
 * @param {Number} [message.userInfo.settings.video.frameRate] The Peer
 *   Stream streaming video maximum frameRate.
 * @param {Boolean} [message.userInfo.settings.video.screenshare=false] The flag
 *   that indicates if the Peer connection Stream object sent
 *   is a screensharing stream or not.
 * @param {String} [message.userInfo.settings.bandwidth] The Peer configuration for
 *   the maximum sending bandwidth. The flags set may or may not work depending
 *   on the browser implementations and how it handles it.
 * @param {String} [message.userInfo.settings.bandwidth.audio] The maximum
 *   sending audio bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the audio bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.video] The maximum
 *   sending video bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the video bitrate to the browser defaults.
 * @param {String} [message.userInfo.settings.bandwidth.data] The maximum
 *   sending data bandwidth bitrate in <var>kb/s</var>. If this is not provided,
 *   it will leave the data bitrate to the browser defaults.
 * @param {JSON} message.userInfo.mediaStatus The Peer Stream mute
 *   settings for both audio and video streamings.
 * @param {Boolean} [message.userInfo.mediaStatus.audioMuted=true] The flag that
 *   indicates if the remote Stream object audio streaming is muted. If
 *   there is no audio streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {Boolean} [message.userInfo.mediaStatus.videoMuted=true] The flag that
 *   indicates if the remote Stream object video streaming is muted. If
 *   there is no video streaming enabled for the Peer, by default,
 *   it is set to <code>true</code>.
 * @param {String} message.agent.name The Peer platform browser or agent name.
 * @param {Number} message.version The Peer platform browser or agent version.
 * @param {Number} message.os The Peer platform name.
 * @param {String} message.type Protocol step <code>"enter"</code>.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {Number} message.weight The generated handshake connection
 *   weight for associated Peer.
 * @param {String} message.sessionType The Peer connection remote Stream streaming
 *   session type. If value is <code>"stream"</code>, the Stream streaming session
 *   is normal user media streaming, else if it is <code>"screensharing"</code>, the
 *   Stream streaming session is screensharing session.
 * @param {String} message.type Protocol step <code>"welcome"</code>.
 * @trigger handshakeProgress, peerJoined
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.4
 */
Skylink.prototype._welcomeHandler = function(message) {
  var targetMid = message.mid;
  var restartConn = false;
  var beOfferer = this._peerPriorityWeight > message.weight;

  log.log([targetMid, null, message.type, 'Received peer\'s response ' +
    'to handshake initiation. Peer\'s information:'], message.userInfo);

  message.agent = (!message.agent) ? 'chrome' : message.agent;
  /*this._enableIceTrickle = (typeof message.enableIceTrickle === 'boolean') ?
    message.enableIceTrickle : this._enableIceTrickle;
  this._enableDataChannel = (typeof message.enableDataChannel === 'boolean') ?
    message.enableDataChannel : this._enableDataChannel;*/

  // mcu has joined
  if (targetMid === 'MCU') {
    log.info([targetMid, 'RTCPeerConnection', 'MCU', 'MCU feature is currently enabled'],
      message);
    log.log([targetMid, null, message.type, 'MCU has ' +
      ((message.weight > -1) ? 'joined and ' : '') + ' responded']);
    this._hasMCU = true;
    this._trigger('serverPeerJoined', targetMid, this.SERVER_PEER_TYPE.MCU);
    log.log([targetMid, null, message.type, 'Always setting as offerer because peer is MCU']);
    beOfferer = true;
  }

  if (this._hasMCU) {
    log.log([targetMid, null, message.type, 'Always setting as offerer because MCU is present']);
    beOfferer = true;
  }

  if (!this._peerInformations[targetMid]) {
    this._peerInformations[targetMid] = message.userInfo || {};
    this._peerInformations[targetMid].agent = {
      name: message.agent,
      version: message.version,
      os: message.os || ''
    };
    // disable mcu for incoming peer sent by MCU
    /*if (message.agent === 'MCU') {
    	this._enableDataChannel = false;

    }*/
    // user is not mcu
    if (targetMid !== 'MCU') {
      this._trigger('peerJoined', targetMid, message.userInfo, false);
    }

    this._trigger('handshakeProgress', this.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  }

  log.debug([targetMid, 'RTCPeerConnection', null, 'Should peer start the connection'], beOfferer);

  var agent = {
    agent: message.agent,
    version: message.version,
    os: message.os
  };

  if (!this._peerConnections[targetMid]) {
    this._addPeer(targetMid, agent, false, restartConn, message.receiveOnly, message.sessionType === 'screensharing');
  }

  if (beOfferer) {
    log.debug([targetMid, 'RTCPeerConnection', null, 'Starting negotiation'], agent);
    this._doOffer(targetMid, agent);

  } else {
    log.debug([targetMid, 'RTCPeerConnection', null, 'Peer has to start the connection. Resending message'], beOfferer);

    this._sendChannelMessage({
      type: this._SIG_MESSAGE_TYPE.WELCOME,
      mid: this._user.sid,
      rid: this._room.id,
      agent: window.webrtcDetectedBrowser,
      version: window.webrtcDetectedVersion,
      os: window.navigator.platform,
      userInfo: this.getPeerInfo(),
      target: targetMid,
      weight: this._peerPriorityWeight,
      sessionType: !!this._mediaScreen ? 'screensharing' : 'stream'
    });
  }
};

/**
 * Handles the OFFER Protocol message event received from the platform signaling.
 * @method _offerHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>OFFER</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.sdp The generated offer session description.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {String} message.type Protocol step <code>"offer"</code>.
 * @trigger handshakeProgress
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.1
 */
Skylink.prototype._offerHandler = function(message) {
  var self = this;
  var targetMid = message.mid;
  var pc = self._peerConnections[targetMid];

  if (!pc) {
    log.error([targetMid, null, message.type, 'Peer connection object ' +
      'not found. Unable to setRemoteDescription for offer']);
    return;
  }

  /*if (pc.localDescription ? !!pc.localDescription.sdp : false) {
    log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
      pc.localDescription);
    return;
  }*/

  log.log([targetMid, null, message.type, 'Received offer from peer. ' +
    'Session description:'], message.sdp);
  var offer = new window.RTCSessionDescription({
    type: message.type,
    sdp: message.sdp
  });
  log.log([targetMid, 'RTCSessionDescription', message.type,
    'Session description object created'], offer);

  // Configure it to force TURN connections by removing non-"relay" candidates
  if (self._forceTURN && !self._enableIceTrickle) {
    if (!self._hasMCU) {
      log.warn([targetMid, 'RTCICECandidate', null, 'Removing non-"relay" candidates from offer ' +
        ' as TURN connections is forced']);

      offer.sdp = offer.sdp.replace(/a=candidate:(?!.*relay.*).*\r\n/g, '');

    } else {
      log.warn([targetMid, 'RTCICECandidate', null, 'Not removing non-"relay"' +
        '" candidates although TURN connections is forced as MCU is present']);
    }
  }

  // This is always the initial state. or even after negotiation is successful
  if (pc.signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
    log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
      '"stable" state for re-negotiation. Dropping message.'], {
        signalingState: pc.signalingState,
        isRestart: !!message.resend
      });
    return;
  }

  // Added checks if there is a current remote sessionDescription being processing before processing this one
  if (pc.processingRemoteSDP) {
    log.warn([targetMid, 'RTCSessionDescription', 'offer',
      'Dropping of setting local offer as there is another ' +
      'sessionDescription being processed ->'], offer);
    return;
  }

  pc.processingRemoteSDP = true;

  pc.setRemoteDescription(offer, function() {
    log.debug([targetMid, 'RTCSessionDescription', message.type, 'Remote description set']);
    pc.setOffer = 'remote';
    pc.processingRemoteSDP = false;
    self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.OFFER, targetMid);
    self._addIceCandidateFromQueue(targetMid);
    self._doAnswer(targetMid);
  }, function(error) {
    self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);

    pc.processingRemoteSDP = false;

    log.error([targetMid, null, message.type, 'Failed setting remote description:'], error);
  });
};


/**
 * Handles the CANDIDATE Protocol message event received from the platform signaling.
 * @method _candidateHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>CANDIDATE</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.id The ICE candidate identifier of the "media stream identification"
 *    for the m-line this candidate is associated with if present.
 *    The value is retrieved from <code>RTCIceCandidate.sdpMid</code>.
 * @param {String} message.label The ICE candidate index (starting at zero) of the m-line
 *    in the SDP this candidate is associated with.
 *    The value is retrieved from <code>RTCIceCandidate.sdpMLineIndex</code>.
 * @param {String} message.candidate The ICE candidate candidate-attribute.
 *    The value is retrieved from <code>RTCIceCandidate.candidate</code>.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {String} message.type Protocol step: <code>"candidate"</code>.
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.1
 */
Skylink.prototype._candidateHandler = function(message) {
  var targetMid = message.mid;
  var pc = this._peerConnections[targetMid];
  log.log([targetMid, null, message.type, 'Received candidate from peer. Candidate config:'], {
    sdp: message.sdp,
    target: message.target,
    candidate: message.candidate,
    label: message.label
  });
  // create ice candidate object
  var messageCan = message.candidate.split(' ');
  var canType = messageCan[7];
  log.log([targetMid, null, message.type, 'Candidate type:'], canType);
  // if (canType !== 'relay' && canType !== 'srflx') {
  // trace('Skipping non relay and non srflx candidates.');
  var index = message.label;
  var candidate = new window.RTCIceCandidate({
    sdpMLineIndex: index,
    candidate: message.candidate,
    //id: message.id,
    sdpMid: message.id
    //label: index
  });

  if (this._forceTURN && canType !== 'relay') {
    if (!this._hasMCU) {
      log.warn([targetMid, 'RTCICECandidate', null, 'Ignoring adding of "' + canType +
        '" candidate as TURN connections is forced'], candidate);
      return;
    }

    log.warn([targetMid, 'RTCICECandidate', null, 'Not ignoring adding of "' + canType +
      '" candidate although TURN connections is forced as MCU is present'], candidate);
  }

  if (pc) {
  	if (pc.signalingState === this.PEER_CONNECTION_STATE.CLOSED) {
  		log.warn([targetMid, null, message.type, 'Peer connection state ' +
  			'is closed. Not adding candidate'], candidate);
	    return;
  	}
    /*if (pc.iceConnectionState === this.ICE_CONNECTION_STATE.CONNECTED) {
      log.debug([targetMid, null, null,
        'Received but not adding Candidate as we are already connected to this peer']);
      return;
    }*/
    // set queue before ice candidate cannot be added before setRemoteDescription.
    // this will cause a black screen of media stream
    if ((pc.setOffer === 'local' && pc.setAnswer === 'remote') ||
      (pc.setAnswer === 'local' && pc.setOffer === 'remote')) {
      pc.addIceCandidate(candidate, this._onAddIceCandidateSuccess, this._onAddIceCandidateFailure);
      // NOTE ALEX: not implemented in chrome yet, need to wait
      // function () { trace('ICE  -  addIceCandidate Succesfull. '); },
      // function (error) { trace('ICE  - AddIceCandidate Failed: ' + error); }
      //);
      log.debug([targetMid, 'RTCIceCandidate', message.type,
        'Added candidate'], candidate);
    } else {
      this._addIceCandidateToQueue(targetMid, candidate);
    }
  } else {
    // Added ice candidate to queue because it may be received before sending the offer
    log.debug([targetMid, 'RTCIceCandidate', message.type,
      'Not adding candidate as peer connection not present'], candidate);
    // NOTE ALEX: if the offer was slow, this can happen
    // we might keep a buffer of candidates to replay after receiving an offer.
    this._addIceCandidateToQueue(targetMid, candidate);
  }

  if (!this._addedCandidates[targetMid]) {
    this._addedCandidates[targetMid] = {
      relay: [],
      host: [],
      srflx: []
    };
  }

  // shouldnt happen but just incase
  if (!this._addedCandidates[targetMid][canType]) {
    this._addedCandidates[targetMid][canType] = [];
  }

  this._addedCandidates[targetMid][canType].push('remote:' + messageCan[4] +
    (messageCan[5] !== '0' ? ':' + messageCan[5] : '') +
    (messageCan[2] ? '?transport=' + messageCan[2].toLowerCase() : ''));
};

/**
 * Handles the ANSWER Protocol message event received from the platform signaling.
 * @method _answerHandler
 * @param {JSON} message The message object received from platform signaling.
 *    This should contain the <code>ANSWER</code> payload.
 * @param {String} message.rid The room ID for identification to the platform signaling connection.
 * @param {String} message.sdp The generated answer session description.
 * @param {String} message.mid The Peer ID associated with this message.
 * @param {String} message.target The targeted Peer ID to receive the message object.
 * @param {String} message.type Protocol step <code>"answer"</code>.
 * @trigger handshakeProgress
 * @private
 * @component Message
 * @for Skylink
 * @since 0.5.1
 */
Skylink.prototype._answerHandler = function(message) {
  var self = this;
  var targetMid = message.mid;

  log.log([targetMid, null, message.type,
    'Received answer from peer. Session description:'], message.sdp);

  var pc = self._peerConnections[targetMid];

  if (!pc) {
    log.error([targetMid, null, message.type, 'Peer connection object ' +
      'not found. Unable to setRemoteDescription for answer']);
    return;
  }

  var answer = new window.RTCSessionDescription({
    type: message.type,
    sdp: message.sdp
  });

  log.log([targetMid, 'RTCSessionDescription', message.type,
    'Session description object created'], answer);

  /*if (pc.remoteDescription ? !!pc.remoteDescription.sdp : false) {
    log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
      pc.remoteDescription);
    return;
  }

  if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
    log.error([targetMid, null, message.type, 'Unable to set peer connection ' +
      'at signalingState "stable". Ignoring remote answer'], pc.signalingState);
    return;
  }*/

  // if firefox and peer is mcu, replace the sdp to suit mcu needs
  if (window.webrtcDetectedType === 'moz' && targetMid === 'MCU') {
    answer.sdp = answer.sdp.replace(/ generation 0/g, '');
    answer.sdp = answer.sdp.replace(/ udp /g, ' UDP ');
  }

  // Configure it to force TURN connections by removing non-"relay" candidates
  if (self._forceTURN && !self._enableIceTrickle) {
    if (!self._hasMCU) {
      log.warn([targetMid, 'RTCICECandidate', null, 'Removing non-"relay" candidates from answer ' +
        ' as TURN connections is forced']);

      answer.sdp = answer.sdp.replace(/a=candidate:(?!.*relay.*).*\r\n/g, '');

    } else {
      log.warn([targetMid, 'RTCICECandidate', null, 'Not removing non-"relay"' +
        '" candidates although TURN connections is forced as MCU is present']);
    }
  }

  // This should be the state after offer is received. or even after negotiation is successful
  if (pc.signalingState !== self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
    log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
      '"have-local-offer" state for re-negotiation. Dropping message.'], {
        signalingState: pc.signalingState,
        isRestart: !!message.restart
      });
    return;
  }

  // Added checks if there is a current remote sessionDescription being processing before processing this one
  if (pc.processingRemoteSDP) {
    log.warn([targetMid, 'RTCSessionDescription', 'answer',
      'Dropping of setting local answer as there is another ' +
      'sessionDescription being processed ->'], answer);
    return;
  }

  pc.processingRemoteSDP = true;

  pc.setRemoteDescription(answer, function() {
    log.debug([targetMid, null, message.type, 'Remote description set']);
    pc.setAnswer = 'remote';
    pc.processingRemoteSDP = false;
    self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ANSWER, targetMid);
    self._addIceCandidateFromQueue(targetMid);

  }, function(error) {
    self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);

    pc.processingRemoteSDP = false;

    log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
      error: error,
      state: pc.signalingState
    });
  });
};

/**
 * Send a message object or string using the platform signaling socket connection
 *   to the list of targeted PeerConnections.
 * To send message objects with DataChannel connections, see
 *   {{#crossLink "Skylink/sendP2PMessage:method"}}sendP2PMessage(){{/crossLink}}.
 * @method sendMessage
 * @param {String|JSON} message The message object.
 * @param {String|Array} [targetPeerId] The array of targeted PeerConnections to
 *   transfer the message object to. Alternatively, you may provide this parameter
 *   as a string to a specific targeted Peer to transfer the message object.
 * @example
 *   // Example 1: Send to all peers
 *   SkylinkDemo.sendMessage("Hi there!"");
 *
 *   // Example 2: Send to a targeted peer
 *   SkylinkDemo.sendMessage("Hi there peer!", targetPeerId);
 * @trigger incomingMessage
 * @component Message
 * @for Skylink
 * @since 0.4.0
 */
Skylink.prototype.sendMessage = function(message, targetPeerId) {
  var params = {
    cid: this._key,
    data: message,
    mid: this._user.sid,
    rid: this._room.id,
    type: this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
  };

  var listOfPeers = Object.keys(this._peerConnections);
  var isPrivate = false;
  var i;

  if(Array.isArray(targetPeerId)) {
    listOfPeers = targetPeerId;
    isPrivate = true;

  } else if (typeof targetPeerId === 'string') {
    listOfPeers = [targetPeerId];
    isPrivate = true;
  }

  if (!isPrivate) {
    log.log([null, 'Socket', null, 'Broadcasting message to peers']);

    this._sendChannelMessage({
      cid: this._key,
      data: message,
      mid: this._user.sid,
      rid: this._room.id,
      type: this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
    });
  }

  for (i = 0; i < listOfPeers.length; i++) {
    var peerId = listOfPeers[i];

    // Ignore MCU peer
    if (peerId === 'MCU') {
      continue;
    }

    if (isPrivate) {
      log.log([peerId, 'Socket', null, 'Sending message to peer']);

      this._sendChannelMessage({
        cid: this._key,
        data: message,
        mid: this._user.sid,
        rid: this._room.id,
        target: peerId,
        type: this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE
      });
    }
  }

  this._trigger('incomingMessage', {
    content: message,
    isPrivate: isPrivate,
    targetPeerId: targetPeerId,
    isDataChannel: false,
    senderPeerId: this._user.sid
  }, this._user.sid, this.getPeerInfo(), true);
};