File: source/socket-message.js

  1. /**
  2. * <blockquote class="info">
  3. * Note that this is used only for SDK developer purposes.<br>
  4. * Current version: <code>0.1.1</code>
  5. * </blockquote>
  6. * The value of the current version of the Signaling socket message protocol.
  7. * @attribute SM_PROTOCOL_VERSION
  8. * @type String
  9. * @for Skylink
  10. * @since 0.6.0
  11. */
  12. Skylink.prototype.SM_PROTOCOL_VERSION = '0.1.1';
  13.  
  14. /**
  15. * Stores the list of socket messaging protocol types.
  16. * See confluence docs for the list based on the current <code>SM_PROTOCOL_VERSION</code>.
  17. * @attribute _SIG_MESSAGE_TYPE
  18. * @type JSON
  19. * @readOnly
  20. * @private
  21. * @for Skylink
  22. * @since 0.5.6
  23. */
  24. Skylink.prototype._SIG_MESSAGE_TYPE = {
  25. JOIN_ROOM: 'joinRoom',
  26. IN_ROOM: 'inRoom',
  27. ENTER: 'enter',
  28. WELCOME: 'welcome',
  29. RESTART: 'restart',
  30. OFFER: 'offer',
  31. ANSWER: 'answer',
  32. CANDIDATE: 'candidate',
  33. BYE: 'bye',
  34. REDIRECT: 'redirect',
  35. UPDATE_USER: 'updateUserEvent',
  36. ROOM_LOCK: 'roomLockEvent',
  37. MUTE_VIDEO: 'muteVideoEvent',
  38. MUTE_AUDIO: 'muteAudioEvent',
  39. PUBLIC_MESSAGE: 'public',
  40. PRIVATE_MESSAGE: 'private',
  41. STREAM: 'stream',
  42. GROUP: 'group',
  43. GET_PEERS: 'getPeers',
  44. PEER_LIST: 'peerList',
  45. INTRODUCE: 'introduce',
  46. INTRODUCE_ERROR: 'introduceError',
  47. APPROACH: 'approach'
  48. };
  49.  
  50. /**
  51. * Stores the flag if MCU environment is enabled.
  52. * @attribute _hasMCU
  53. * @type Boolean
  54. * @private
  55. * @for Skylink
  56. * @since 0.5.4
  57. */
  58. Skylink.prototype._hasMCU = false;
  59.  
  60. /**
  61. * Stores the list of socket messaging protocol types to queue when sent less than a second interval.
  62. * @attribute _groupMessageList
  63. * @type Array
  64. * @private
  65. * @for Skylink
  66. * @since 0.5.10
  67. */
  68. Skylink.prototype._groupMessageList = [
  69. Skylink.prototype._SIG_MESSAGE_TYPE.STREAM,
  70. Skylink.prototype._SIG_MESSAGE_TYPE.UPDATE_USER,
  71. Skylink.prototype._SIG_MESSAGE_TYPE.ROOM_LOCK,
  72. Skylink.prototype._SIG_MESSAGE_TYPE.MUTE_AUDIO,
  73. Skylink.prototype._SIG_MESSAGE_TYPE.MUTE_VIDEO,
  74. Skylink.prototype._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
  75. ];
  76.  
  77. /**
  78. * Stores the flag that indicates if MCU is available in the Room.
  79. * If App Key enables MCU but this is false, this means likely there are problems connecting to the MCU server.
  80. * @attribute _hasMCU
  81. * @type Boolean
  82. * @private
  83. * @for Skylink
  84. * @since 0.5.4
  85. */
  86. Skylink.prototype._hasMCU = false;
  87.  
  88.  
  89. /**
  90. * Stores the flag that indicates if User should only receive Stream from Peer connections but
  91. * do not send User's Stream to Peer connections.
  92. * @attribute _receiveOnly
  93. * @type Boolean
  94. * @private
  95. * @for Skylink
  96. * @since 0.5.10
  97. */
  98. Skylink.prototype._receiveOnly = false;
  99.  
  100. /**
  101. * Stores the list of Peer messages timestamp.
  102. * @attribute _peerMessagesStamps
  103. * @type JSON
  104. * @private
  105. * @for Skylink
  106. * @since 0.6.15
  107. */
  108. Skylink.prototype._peerMessagesStamps = {};
  109.  
  110. /**
  111. * <blockquote class="info">
  112. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  113. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  114. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  115. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  116. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  117. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  118. * sent within less than an interval.
  119. * </blockquote>
  120. * Function that sends a message to Peers via the Signaling socket connection.
  121. * @method sendMessage
  122. * @param {String|JSON} message The message.
  123. * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
  124. * - When provided as an Array, it will send the message to only Peers which IDs are in the list.
  125. * - When not provided, it will broadcast the message to all connected Peers in the Room.
  126. * @example
  127. * // Example 1: Broadcasting to all Peers
  128. * skylinkDemo.sendMessage("Hi all!");
  129. *
  130. * // Example 2: Sending to specific Peers
  131. * var peersInExclusiveParty = [];
  132. *
  133. * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
  134. * if (isSelf) return;
  135. * if (peerInfo.userData.exclusive) {
  136. * peersInExclusiveParty.push(peerId);
  137. * }
  138. * });
  139. *
  140. * function updateExclusivePartyStatus (message) {
  141. * skylinkDemo.sendMessage(message, peersInExclusiveParty);
  142. * }
  143. * @trigger <ol class="desc-seq">
  144. * <li>Sends socket connection message to all targeted Peers via Signaling server. <ol>
  145. * <li><a href="#event_incomingMessage"><code>incomingMessage</code> event</a> triggers parameter payload
  146. * <code>message.isDataChannel</code> value as <code>false</code>.</li></ol></li></ol>
  147. * @for Skylink
  148. * @since 0.4.0
  149. */
  150. Skylink.prototype.sendMessage = function(message, targetPeerId) {
  151. var params = {
  152. cid: this._key,
  153. data: message,
  154. mid: this._user.sid,
  155. rid: this._room.id,
  156. type: this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
  157. };
  158.  
  159. var listOfPeers = Object.keys(this._peerConnections);
  160. var isPrivate = false;
  161. var i;
  162.  
  163. if(Array.isArray(targetPeerId)) {
  164. listOfPeers = targetPeerId;
  165. isPrivate = true;
  166.  
  167. } else if (typeof targetPeerId === 'string') {
  168. listOfPeers = [targetPeerId];
  169. isPrivate = true;
  170. }
  171.  
  172. if (!isPrivate) {
  173. log.log([null, 'Socket', null, 'Broadcasting message to peers']);
  174.  
  175. this._sendChannelMessage({
  176. cid: this._key,
  177. data: message,
  178. mid: this._user.sid,
  179. rid: this._room.id,
  180. type: this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
  181. });
  182. }
  183.  
  184. for (i = 0; i < listOfPeers.length; i++) {
  185. var peerId = listOfPeers[i];
  186.  
  187. // Ignore MCU peer
  188. if (peerId === 'MCU') {
  189. continue;
  190. }
  191.  
  192. if (isPrivate) {
  193. log.log([peerId, 'Socket', null, 'Sending message to peer']);
  194.  
  195. this._sendChannelMessage({
  196. cid: this._key,
  197. data: message,
  198. mid: this._user.sid,
  199. rid: this._room.id,
  200. target: peerId,
  201. type: this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE
  202. });
  203. }
  204. }
  205.  
  206. this._trigger('incomingMessage', {
  207. content: message,
  208. isPrivate: isPrivate,
  209. targetPeerId: targetPeerId,
  210. isDataChannel: false,
  211. senderPeerId: this._user.sid
  212. }, this._user.sid, this.getPeerInfo(), true);
  213. };
  214.  
  215. /**
  216. * Function that process and parses the socket message string received from the Signaling.
  217. * @method _processSigMessage
  218. * @private
  219. * @for Skylink
  220. * @since 0.1.0
  221. */
  222. Skylink.prototype._processSigMessage = function(messageString) {
  223. var message = JSON.parse(messageString);
  224. if (message.type === this._SIG_MESSAGE_TYPE.GROUP) {
  225. log.debug('Bundle of ' + message.lists.length + ' messages');
  226. for (var i = 0; i < message.lists.length; i++) {
  227. this._processSingleMessage(JSON.parse(message.lists[i]));
  228. }
  229. } else {
  230. this._processSingleMessage(message);
  231. }
  232. };
  233.  
  234. /**
  235. * Function that handles and processes the socket message received.
  236. * @method _processingSingleMessage
  237. * @private
  238. * @for Skylink
  239. * @since 0.1.0
  240. */
  241. Skylink.prototype._processSingleMessage = function(message) {
  242. this._trigger('channelMessage', message);
  243. var origin = message.mid;
  244. if (!origin || origin === this._user.sid) {
  245. origin = 'Server';
  246. }
  247. log.debug([origin, null, null, 'Received from peer ->'], message.type);
  248. if (message.mid === this._user.sid &&
  249. message.type !== this._SIG_MESSAGE_TYPE.REDIRECT &&
  250. message.type !== this._SIG_MESSAGE_TYPE.IN_ROOM) {
  251. log.debug([origin, null, null, 'Ignoring message ->'], message.type);
  252. return;
  253. }
  254. switch (message.type) {
  255. //--- BASIC API Messages ----
  256. case this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE:
  257. this._publicMessageHandler(message);
  258. break;
  259. case this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE:
  260. this._privateMessageHandler(message);
  261. break;
  262. case this._SIG_MESSAGE_TYPE.IN_ROOM:
  263. this._inRoomHandler(message);
  264. break;
  265. case this._SIG_MESSAGE_TYPE.ENTER:
  266. this._enterHandler(message);
  267. break;
  268. case this._SIG_MESSAGE_TYPE.WELCOME:
  269. this._welcomeHandler(message);
  270. break;
  271. case this._SIG_MESSAGE_TYPE.RESTART:
  272. this._restartHandler(message);
  273. break;
  274. case this._SIG_MESSAGE_TYPE.OFFER:
  275. this._offerHandler(message);
  276. break;
  277. case this._SIG_MESSAGE_TYPE.ANSWER:
  278. this._answerHandler(message);
  279. break;
  280. case this._SIG_MESSAGE_TYPE.CANDIDATE:
  281. this._candidateHandler(message);
  282. break;
  283. case this._SIG_MESSAGE_TYPE.BYE:
  284. this._byeHandler(message);
  285. break;
  286. case this._SIG_MESSAGE_TYPE.REDIRECT:
  287. this._redirectHandler(message);
  288. break;
  289. //--- ADVANCED API Messages ----
  290. case this._SIG_MESSAGE_TYPE.UPDATE_USER:
  291. this._updateUserEventHandler(message);
  292. break;
  293. case this._SIG_MESSAGE_TYPE.MUTE_VIDEO:
  294. this._muteVideoEventHandler(message);
  295. break;
  296. case this._SIG_MESSAGE_TYPE.MUTE_AUDIO:
  297. this._muteAudioEventHandler(message);
  298. break;
  299. case this._SIG_MESSAGE_TYPE.STREAM:
  300. this._streamEventHandler(message);
  301. break;
  302. case this._SIG_MESSAGE_TYPE.ROOM_LOCK:
  303. this._roomLockEventHandler(message);
  304. break;
  305. case this._SIG_MESSAGE_TYPE.PEER_LIST:
  306. this._peerListEventHandler(message);
  307. break;
  308. case this._SIG_MESSAGE_TYPE.INTRODUCE_ERROR:
  309. this._introduceErrorEventHandler(message);
  310. break;
  311. case this._SIG_MESSAGE_TYPE.APPROACH:
  312. this._approachEventHandler(message);
  313. break;
  314. default:
  315. log.error([message.mid, null, null, 'Unsupported message ->'], message.type);
  316. break;
  317. }
  318. };
  319.  
  320. /**
  321. * Function that handles the "peerList" socket message received.
  322. * See confluence docs for the "peerList" expected properties to be received
  323. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  324. * @method _peerListEventHandler
  325. * @private
  326. * @for Skylink
  327. * @since 0.6.1
  328. */
  329. Skylink.prototype._peerListEventHandler = function(message){
  330. var self = this;
  331. self._peerList = message.result;
  332. log.log(['Server', null, message.type, 'Received list of peers'], self._peerList);
  333. self._trigger('getPeersStateChange',self.GET_PEERS_STATE.RECEIVED, self._user.sid, self._peerList);
  334. };
  335.  
  336. /**
  337. * Function that handles the "introduceError" socket message received.
  338. * See confluence docs for the "introduceError" expected properties to be received
  339. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  340. * @method _introduceErrorEventHandler
  341. * @private
  342. * @for Skylink
  343. * @since 0.6.1
  344. */
  345. Skylink.prototype._introduceErrorEventHandler = function(message){
  346. var self = this;
  347. log.log(['Server', null, message.type, 'Introduce failed. Reason: '+message.reason]);
  348. self._trigger('introduceStateChange',self.INTRODUCE_STATE.ERROR, self._user.sid,
  349. message.sendingPeerId, message.receivingPeerId, message.reason);
  350. };
  351.  
  352. /**
  353. * Function that handles the "approach" socket message received.
  354. * See confluence docs for the "approach" expected properties to be received
  355. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  356. * @method _approachEventHandler
  357. * @private
  358. * @for Skylink
  359. * @since 0.6.1
  360. */
  361. Skylink.prototype._approachEventHandler = function(message){
  362. var self = this;
  363. log.log(['Server', null, message.type, 'Approaching peer'], message.target);
  364. // self._room.connection.peerConfig = self._setIceServers(message.pc_config);
  365. // self._inRoom = true;
  366. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  367. self._sendChannelMessage({
  368. type: self._SIG_MESSAGE_TYPE.ENTER,
  369. mid: self._user.sid,
  370. rid: self._room.id,
  371. agent: window.webrtcDetectedBrowser,
  372. version: window.webrtcDetectedVersion,
  373. os: window.navigator.platform,
  374. userInfo: self._getUserInfo(),
  375. receiveOnly: self._receiveOnly,
  376. sessionType: !!self._streams.screenshare ? 'screensharing' : 'stream',
  377. target: message.target,
  378. weight: self._peerPriorityWeight,
  379. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  380. });
  381. };
  382.  
  383. /**
  384. * Function that handles the "redirect" socket message received.
  385. * See confluence docs for the "redirect" expected properties to be received
  386. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  387. * @method _redirectHandler
  388. * @private
  389. * @for Skylink
  390. * @since 0.5.1
  391. */
  392. Skylink.prototype._redirectHandler = function(message) {
  393. log.log(['Server', null, message.type, 'System action warning:'], {
  394. message: message.info,
  395. reason: message.reason,
  396. action: message.action
  397. });
  398.  
  399. if (message.action === this.SYSTEM_ACTION.REJECT) {
  400. for (var key in this._peerConnections) {
  401. if (this._peerConnections.hasOwnProperty(key)) {
  402. this._removePeer(key);
  403. }
  404. }
  405. }
  406.  
  407. // Handle the differences provided in Signaling server
  408. if (message.reason === 'toClose') {
  409. message.reason = 'toclose';
  410. }
  411.  
  412. this._trigger('systemAction', message.action, message.info, message.reason);
  413. };
  414.  
  415. /**
  416. * Function that handles the "updateUserEvent" socket message received.
  417. * See confluence docs for the "updateUserEvent" expected properties to be received
  418. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  419. * @method _updateUserEventHandler
  420. * @private
  421. * @for Skylink
  422. * @since 0.2.0
  423. */
  424. Skylink.prototype._updateUserEventHandler = function(message) {
  425. var targetMid = message.mid;
  426. log.log([targetMid, null, message.type, 'Peer updated userData:'], message.userData);
  427. if (this._peerInformations[targetMid]) {
  428. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  429. if (message.stamp < this._peerMessagesStamps[targetMid].userData) {
  430. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  431. return;
  432. }
  433. this._peerMessagesStamps[targetMid].userData = message.stamp;
  434. }
  435. this._peerInformations[targetMid].userData = message.userData || {};
  436. this._trigger('peerUpdated', targetMid,
  437. this.getPeerInfo(targetMid), false);
  438. } else {
  439. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  440. }
  441. };
  442.  
  443. /**
  444. * Function that handles the "roomLockEvent" socket message received.
  445. * See confluence docs for the "roomLockEvent" expected properties to be received
  446. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  447. * @method _roomLockEventHandler
  448. * @private
  449. * @for Skylink
  450. * @since 0.2.0
  451. */
  452. Skylink.prototype._roomLockEventHandler = function(message) {
  453. var targetMid = message.mid;
  454. log.log([targetMid, message.type, 'Room lock status:'], message.lock);
  455. this._trigger('roomLock', message.lock, targetMid,
  456. this.getPeerInfo(targetMid), false);
  457. };
  458.  
  459. /**
  460. * Function that handles the "muteAudioEvent" socket message received.
  461. * See confluence docs for the "muteAudioEvent" expected properties to be received
  462. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  463. * @method _muteAudioEventHandler
  464. * @private
  465. * @for Skylink
  466. * @since 0.2.0
  467. */
  468. Skylink.prototype._muteAudioEventHandler = function(message) {
  469. var targetMid = message.mid;
  470. log.log([targetMid, null, message.type, 'Peer\'s audio muted:'], message.muted);
  471. if (this._peerInformations[targetMid]) {
  472. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  473. if (message.stamp < this._peerMessagesStamps[targetMid].audioMuted) {
  474. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  475. return;
  476. }
  477. this._peerMessagesStamps[targetMid].audioMuted = message.stamp;
  478. }
  479. this._peerInformations[targetMid].mediaStatus.audioMuted = message.muted;
  480. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  481. this._peerInformations[targetMid].settings.video &&
  482. this._peerInformations[targetMid].settings.video.screenshare);
  483. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  484. } else {
  485. log.log([targetMid, message.type, 'Peer does not have any user information']);
  486. }
  487. };
  488.  
  489. /**
  490. * Function that handles the "muteVideoEvent" socket message received.
  491. * See confluence docs for the "muteVideoEvent" expected properties to be received
  492. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  493. * @method _muteVideoEventHandler
  494. * @private
  495. * @for Skylink
  496. * @since 0.2.0
  497. */
  498. Skylink.prototype._muteVideoEventHandler = function(message) {
  499. var targetMid = message.mid;
  500. log.log([targetMid, null, message.type, 'Peer\'s video muted:'], message.muted);
  501. if (this._peerInformations[targetMid]) {
  502. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  503. if (message.stamp < this._peerMessagesStamps[targetMid].videoMuted) {
  504. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  505. return;
  506. }
  507. this._peerMessagesStamps[targetMid].videoMuted = message.stamp;
  508. }
  509. this._peerInformations[targetMid].mediaStatus.videoMuted = message.muted;
  510. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  511. this._peerInformations[targetMid].settings.video &&
  512. this._peerInformations[targetMid].settings.video.screenshare);
  513. this._trigger('peerUpdated', targetMid,
  514. this.getPeerInfo(targetMid), false);
  515. } else {
  516. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  517. }
  518. };
  519.  
  520. /**
  521. * Function that handles the "stream" socket message received.
  522. * See confluence docs for the "stream" expected properties to be received
  523. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  524. * @method _streamEventHandler
  525. * @private
  526. * @for Skylink
  527. * @since 0.2.0
  528. */
  529. Skylink.prototype._streamEventHandler = function(message) {
  530. var targetMid = message.mid;
  531. log.log([targetMid, null, message.type, 'Peer\'s stream status:'], message.status);
  532.  
  533. if (this._peerInformations[targetMid]) {
  534.  
  535. if (message.status === 'ended') {
  536. this._trigger('streamEnded', targetMid, this.getPeerInfo(targetMid),
  537. false, message.sessionType === 'screensharing', message.streamId);
  538. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  539.  
  540. if (this._peerConnections[targetMid]) {
  541. this._peerConnections[targetMid].hasStream = false;
  542. if (message.sessionType === 'screensharing') {
  543. this._peerConnections[targetMid].hasScreen = false;
  544. }
  545. } else {
  546. log.log([targetMid, null, message.type, 'Peer connection not found']);
  547. }
  548. } else if (message.status === 'check') {
  549. if (!message.streamId) {
  550. return;
  551. }
  552.  
  553. // Prevent restarts unless its stable
  554. if (this._peerConnections[targetMid] && this._peerConnections[targetMid].signalingState ===
  555. this.PEER_CONNECTION_STATE.STABLE) {
  556. var streams = this._peerConnections[targetMid].getRemoteStreams();
  557. if (streams.length > 0 && message.streamId !== (streams[0].id || streams[0].label)) {
  558. this._sendChannelMessage({
  559. type: this._SIG_MESSAGE_TYPE.RESTART,
  560. mid: this._user.sid,
  561. rid: this._room.id,
  562. agent: window.webrtcDetectedBrowser,
  563. version: window.webrtcDetectedVersion,
  564. os: window.navigator.platform,
  565. userInfo: this._getUserInfo(),
  566. target: targetMid,
  567. weight: this._peerPriorityWeight,
  568. enableIceTrickle: this._enableIceTrickle,
  569. enableDataChannel: this._enableDataChannel,
  570. receiveOnly: this._peerConnections[targetMid] && this._peerConnections[targetMid].receiveOnly,
  571. sessionType: !!this._streams.screenshare ? 'screensharing' : 'stream',
  572. // SkylinkJS parameters (copy the parameters from received message parameters)
  573. isConnectionRestart: !!message.isConnectionRestart,
  574. lastRestart: message.lastRestart,
  575. explicit: !!message.explicit,
  576. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  577. });
  578. }
  579. }
  580. }
  581.  
  582. } else {
  583. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  584. }
  585. };
  586.  
  587. /**
  588. * Function that handles the "bye" socket message received.
  589. * See confluence docs for the "bye" expected properties to be received
  590. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  591. * @method _byeHandler
  592. * @private
  593. * @for Skylink
  594. * @since 0.1.0
  595. */
  596. Skylink.prototype._byeHandler = function(message) {
  597. var targetMid = message.mid;
  598. var selfId = (this._user || {}).sid;
  599.  
  600. if (selfId !== targetMid){
  601. log.log([targetMid, null, message.type, 'Peer has left the room']);
  602. this._removePeer(targetMid);
  603. } else {
  604. log.log([targetMid, null, message.type, 'Self has left the room']);
  605. }
  606. };
  607.  
  608. /**
  609. * Function that handles the "private" socket message received.
  610. * See confluence docs for the "private" expected properties to be received
  611. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  612. * @method _privateMessageHandler
  613. * @private
  614. * @for Skylink
  615. * @since 0.4.0
  616. */
  617. Skylink.prototype._privateMessageHandler = function(message) {
  618. var targetMid = message.mid;
  619. log.log([targetMid, null, message.type,
  620. 'Received private message from peer:'], message.data);
  621. this._trigger('incomingMessage', {
  622. content: message.data,
  623. isPrivate: true,
  624. targetPeerId: message.target, // is not null if there's user
  625. isDataChannel: false,
  626. senderPeerId: targetMid
  627. }, targetMid, this.getPeerInfo(targetMid), false);
  628. };
  629.  
  630. /**
  631. * Function that handles the "public" socket message received.
  632. * See confluence docs for the "public" expected properties to be received
  633. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  634. * @method _publicMessageHandler
  635. * @private
  636. * @for Skylink
  637. * @since 0.4.0
  638. */
  639. Skylink.prototype._publicMessageHandler = function(message) {
  640. var targetMid = message.mid;
  641. log.log([targetMid, null, message.type,
  642. 'Received public message from peer:'], message.data);
  643. this._trigger('incomingMessage', {
  644. content: message.data,
  645. isPrivate: false,
  646. targetPeerId: null, // is not null if there's user
  647. isDataChannel: false,
  648. senderPeerId: targetMid
  649. }, targetMid, this.getPeerInfo(targetMid), false);
  650. };
  651.  
  652. /**
  653. * Function that handles the "inRoom" socket message received.
  654. * See confluence docs for the "inRoom" expected properties to be received
  655. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  656. * @method _inRoomHandler
  657. * @private
  658. * @for Skylink
  659. * @since 0.1.0
  660. */
  661. Skylink.prototype._inRoomHandler = function(message) {
  662. var self = this;
  663. log.log(['Server', null, message.type, 'User is now in the room and ' +
  664. 'functionalities are now available. Config received:'], message.pc_config);
  665. self._room.connection.peerConfig = self._setIceServers(message.pc_config);
  666. self._inRoom = true;
  667. self._user.sid = message.sid;
  668. self._peerPriorityWeight = (new Date()).getTime();
  669.  
  670. self._trigger('peerJoined', self._user.sid, self.getPeerInfo(), true);
  671. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  672.  
  673. if (typeof message.tieBreaker === 'number') {
  674. self._peerPriorityWeight = message.tieBreaker;
  675. }
  676.  
  677. // Make Firefox the answerer always when connecting with other browsers
  678. if (window.webrtcDetectedBrowser === 'firefox') {
  679. log.warn('Decreasing weight for Firefox browser connection');
  680.  
  681. self._peerPriorityWeight -= 100000000000;
  682. }
  683.  
  684. if (self._streams.screenshare && self._streams.screenshare.stream) {
  685. self._trigger('incomingStream', self._user.sid, self._streams.screenshare.stream, true, self.getPeerInfo());
  686. } else if (self._streams.userMedia && self._streams.userMedia.stream) {
  687. self._trigger('incomingStream', self._user.sid, self._streams.userMedia.stream, true, self.getPeerInfo());
  688. }
  689. // NOTE ALEX: should we wait for local streams?
  690. // or just go with what we have (if no stream, then one way?)
  691. // do we hardcode the logic here, or give the flexibility?
  692. // It would be better to separate, do we could choose with whom
  693. // we want to communicate, instead of connecting automatically to all.
  694. self._sendChannelMessage({
  695. type: self._SIG_MESSAGE_TYPE.ENTER,
  696. mid: self._user.sid,
  697. rid: self._room.id,
  698. agent: window.webrtcDetectedBrowser,
  699. version: window.webrtcDetectedVersion,
  700. os: window.navigator.platform,
  701. userInfo: self._getUserInfo(),
  702. receiveOnly: self._receiveOnly,
  703. sessionType: !!self._streams.screenshare ? 'screensharing' : 'stream',
  704. weight: self._peerPriorityWeight,
  705. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  706. });
  707. };
  708.  
  709. /**
  710. * Function that handles the "enter" socket message received.
  711. * See confluence docs for the "enter" expected properties to be received
  712. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  713. * @method _enterHandler
  714. * @private
  715. * @for Skylink
  716. * @since 0.5.1
  717. */
  718. Skylink.prototype._enterHandler = function(message) {
  719. var self = this;
  720. var targetMid = message.mid;
  721. var isNewPeer = false;
  722.  
  723. log.log([targetMid, null, message.type, 'Received Peer\'s presence ->'], message.userInfo);
  724.  
  725. if (!self._peerInformations[targetMid]) {
  726. isNewPeer = true;
  727. self._addPeer(targetMid, {
  728. agent: message.agent,
  729. version: message.version,
  730. os: message.os
  731. }, false, false, message.receiveOnly, message.sessionType === 'screensharing');
  732.  
  733. self._peerInformations[targetMid] = message.userInfo || {};
  734. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  735. userData: 0,
  736. audioMuted: 0,
  737. videoMuted: 0
  738. };
  739. self._peerInformations[targetMid].agent = {
  740. name: message.agent,
  741. version: message.version,
  742. os: message.os || '',
  743. pluginVersion: message.temasysPluginVersion
  744. };
  745.  
  746. if (targetMid !== 'MCU') {
  747. self._trigger('peerJoined', targetMid, message.userInfo, false);
  748.  
  749. } else {
  750. log.info([targetMid, 'RTCPeerConnection', 'MCU', 'MCU feature has been enabled'], message);
  751. log.log([targetMid, null, message.type, 'MCU has joined'], message.userInfo);
  752. this._hasMCU = true;
  753. this._trigger('serverPeerJoined', targetMid, this.SERVER_PEER_TYPE.MCU);
  754. }
  755.  
  756. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);
  757. }
  758.  
  759. self._sendChannelMessage({
  760. type: self._SIG_MESSAGE_TYPE.WELCOME,
  761. mid: self._user.sid,
  762. rid: self._room.id,
  763. receiveOnly: self._peerConnections[targetMid] ?
  764. !!self._peerConnections[targetMid].receiveOnly : false,
  765. enableIceTrickle: self._enableIceTrickle,
  766. enableDataChannel: self._enableDataChannel,
  767. agent: window.webrtcDetectedBrowser,
  768. version: window.webrtcDetectedVersion,
  769. os: window.navigator.platform,
  770. userInfo: self._getUserInfo(),
  771. target: targetMid,
  772. weight: self._peerPriorityWeight,
  773. sessionType: !!self._streams.screenshare ? 'screensharing' : 'stream',
  774. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  775. });
  776.  
  777. if (isNewPeer) {
  778. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  779. }
  780. };
  781.  
  782. /**
  783. * Function that handles the "restart" socket message received.
  784. * See confluence docs for the "restart" expected properties to be received
  785. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  786. * @method _restartHandler
  787. * @private
  788. * @for Skylink
  789. * @since 0.5.6
  790. */
  791. Skylink.prototype._restartHandler = function(message){
  792. var self = this;
  793. var targetMid = message.mid;
  794.  
  795. if (!self._peerInformations[targetMid]) {
  796. log.error([targetMid, null, null, 'Peer does not have an existing ' +
  797. 'session. Ignoring restart process.']);
  798. return;
  799. }
  800.  
  801. // NOTE: for now we ignore, but we should take-note to implement in the near future
  802. if (self._hasMCU) {
  803. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false);
  804. return;
  805. }
  806.  
  807. self.lastRestart = message.lastRestart || Date.now() || function() { return +new Date(); };
  808.  
  809. if (!self._peerConnections[targetMid]) {
  810. log.error([targetMid, null, null, 'Peer does not have an existing ' +
  811. 'connection. Unable to restart']);
  812. return;
  813. }
  814.  
  815. // mcu has re-joined
  816. // NOTE: logic trip since _hasMCU flags are ignored, this could result in failure perhaps?
  817. if (targetMid === 'MCU') {
  818. log.log([targetMid, null, message.type, 'MCU has restarted its connection']);
  819. self._hasMCU = true;
  820. }
  821.  
  822. // Uncomment because we do not need this
  823. //self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  824.  
  825. message.agent = (!message.agent) ? 'chrome' : message.agent;
  826. /*self._enableIceTrickle = (typeof message.enableIceTrickle === 'boolean') ?
  827. message.enableIceTrickle : self._enableIceTrickle;
  828. self._enableDataChannel = (typeof message.enableDataChannel === 'boolean') ?
  829. message.enableDataChannel : self._enableDataChannel;*/
  830.  
  831. // re-add information
  832. self._peerInformations[targetMid] = message.userInfo || {};
  833. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  834. userData: 0,
  835. audioMuted: 0,
  836. videoMuted: 0
  837. };
  838. self._peerInformations[targetMid].agent = {
  839. name: message.agent,
  840. version: message.version,
  841. os: message.os || '',
  842. pluginVersion: message.temasysPluginVersion
  843. };
  844.  
  845. var agent = (self.getPeerInfo(targetMid) || {}).agent || {};
  846.  
  847. // This variable is not used
  848. //var peerConnectionStateStable = false;
  849.  
  850. log.info([targetMid, 'RTCPeerConnection', null, 'Received restart request from peer'], message);
  851. // we are no longer adding any peer
  852. /*self._addPeer(targetMid, {
  853. agent: message.agent,
  854. version: message.version,
  855. os: message.os
  856. }, true, true, message.receiveOnly, message.sessionType === 'screensharing');*/
  857.  
  858. // Make peer with highest weight do the offer
  859. if (self._peerPriorityWeight > message.weight) {
  860. log.debug([targetMid, 'RTCPeerConnection', null, 'Restarting negotiation'], agent);
  861. self._doOffer(targetMid, {
  862. agent: agent.name,
  863. version: agent.version,
  864. os: agent.os
  865. }, true);
  866.  
  867. } else {
  868. log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start re-negotiation'], agent);
  869. self._sendChannelMessage({
  870. type: self._SIG_MESSAGE_TYPE.RESTART,
  871. mid: self._user.sid,
  872. rid: self._room.id,
  873. agent: window.webrtcDetectedBrowser,
  874. version: window.webrtcDetectedVersion,
  875. os: window.navigator.platform,
  876. userInfo: self._getUserInfo(),
  877. target: targetMid,
  878. weight: self._peerPriorityWeight,
  879. enableIceTrickle: self._enableIceTrickle,
  880. enableDataChannel: self._enableDataChannel,
  881. receiveOnly: self._peerConnections[targetMid] && self._peerConnections[targetMid].receiveOnly,
  882. sessionType: !!self._streams.screenshare ? 'screensharing' : 'stream',
  883. // SkylinkJS parameters (copy the parameters from received message parameters)
  884. isConnectionRestart: !!message.isConnectionRestart,
  885. lastRestart: message.lastRestart,
  886. explicit: !!message.explicit,
  887. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  888. });
  889. }
  890.  
  891. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false);
  892.  
  893. // following the previous logic to do checker always
  894. self._startPeerConnectionHealthCheck(targetMid, false);
  895. };
  896.  
  897. /**
  898. * Function that handles the "welcome" socket message received.
  899. * See confluence docs for the "welcome" expected properties to be received
  900. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  901. * @method _welcomeHandler
  902. * @private
  903. * @for Skylink
  904. * @since 0.5.4
  905. */
  906. Skylink.prototype._welcomeHandler = function(message) {
  907. var targetMid = message.mid;
  908. var restartConn = false;
  909. var beOfferer = this._peerPriorityWeight > message.weight;
  910. var isNewPeer = false;
  911.  
  912. log.log([targetMid, null, message.type, 'Received Peer\'s presence ->'], message.userInfo);
  913.  
  914. // We shouldn't assume as chrome
  915. message.agent = (!message.agent) ? 'unknown' : message.agent;
  916.  
  917. var agent = {
  918. agent: message.agent,
  919. version: message.version,
  920. os: message.os
  921. };
  922.  
  923. if (!this._peerInformations[targetMid]) {
  924. this._peerInformations[targetMid] = message.userInfo || {};
  925. this._peerMessagesStamps[targetMid] = this._peerMessagesStamps[targetMid] || {
  926. userData: 0,
  927. audioMuted: 0,
  928. videoMuted: 0
  929. };
  930. this._peerInformations[targetMid].agent = {
  931. name: message.agent,
  932. version: message.version,
  933. os: message.os || '',
  934. pluginVersion: message.temasysPluginVersion
  935. };
  936. // disable mcu for incoming peer sent by MCU
  937. /*if (message.agent === 'MCU') {
  938. this._enableDataChannel = false;
  939.  
  940. }*/
  941. // user is not mcu
  942. if (targetMid !== 'MCU') {
  943. this._trigger('peerJoined', targetMid, message.userInfo, false);
  944.  
  945. } else {
  946. log.info([targetMid, 'RTCPeerConnection', 'MCU', 'MCU feature is currently enabled'], message);
  947. log.log([targetMid, null, message.type, 'MCU has ' +
  948. ((message.weight > -1) ? 'joined and ' : '') + ' responded']);
  949. this._hasMCU = true;
  950. this._trigger('serverPeerJoined', targetMid, this.SERVER_PEER_TYPE.MCU);
  951. log.log([targetMid, null, message.type, 'Always setting as offerer because peer is MCU']);
  952. beOfferer = true;
  953. }
  954.  
  955. if (!this._peerConnections[targetMid]) {
  956. this._addPeer(targetMid, agent, false, restartConn, message.receiveOnly, message.sessionType === 'screensharing');
  957. }
  958.  
  959. this._trigger('handshakeProgress', this.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  960. }
  961.  
  962. if (this._hasMCU) {
  963. log.log([targetMid, null, message.type, 'Always setting as offerer because MCU is present']);
  964. beOfferer = true;
  965. }
  966.  
  967. /*this._enableIceTrickle = (typeof message.enableIceTrickle === 'boolean') ?
  968. message.enableIceTrickle : this._enableIceTrickle;
  969. this._enableDataChannel = (typeof message.enableDataChannel === 'boolean') ?
  970. message.enableDataChannel : this._enableDataChannel;*/
  971.  
  972. log.debug([targetMid, 'RTCPeerConnection', null, 'Peer should start connection ->'], beOfferer);
  973.  
  974. if (beOfferer) {
  975. log.debug([targetMid, 'RTCPeerConnection', null, 'Starting negotiation'], agent);
  976. this._doOffer(targetMid, agent);
  977.  
  978. } else {
  979. log.debug([targetMid, 'RTCPeerConnection', null, 'Peer has to start the connection. Resending message'], beOfferer);
  980.  
  981. this._sendChannelMessage({
  982. type: this._SIG_MESSAGE_TYPE.WELCOME,
  983. mid: this._user.sid,
  984. rid: this._room.id,
  985. agent: window.webrtcDetectedBrowser,
  986. version: window.webrtcDetectedVersion,
  987. os: window.navigator.platform,
  988. userInfo: this._getUserInfo(),
  989. target: targetMid,
  990. weight: this._peerPriorityWeight,
  991. sessionType: !!this._streams.screenshare ? 'screensharing' : 'stream',
  992. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null
  993. });
  994. }
  995. };
  996.  
  997. /**
  998. * Function that handles the "offer" socket message received.
  999. * See confluence docs for the "offer" expected properties to be received
  1000. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1001. * @method _offerHandler
  1002. * @private
  1003. * @for Skylink
  1004. * @since 0.5.1
  1005. */
  1006. Skylink.prototype._offerHandler = function(message) {
  1007. var self = this;
  1008. var targetMid = message.mid;
  1009. var pc = self._peerConnections[targetMid];
  1010.  
  1011. if (!pc) {
  1012. log.error([targetMid, null, message.type, 'Peer connection object ' +
  1013. 'not found. Unable to setRemoteDescription for offer']);
  1014. return;
  1015. }
  1016.  
  1017. /*if (pc.localDescription ? !!pc.localDescription.sdp : false) {
  1018. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1019. pc.localDescription);
  1020. return;
  1021. }*/
  1022.  
  1023. // Add-on by Web SDK fixes
  1024. if (message.userInfo && typeof message.userInfo === 'object') {
  1025. self._peerInformations[targetMid].settings = message.userInfo.settings;
  1026. self._peerInformations[targetMid].mediaStatus = message.userInfo.mediaStatus;
  1027. self._peerInformations[targetMid].userData = message.userInfo.userData;
  1028. }
  1029.  
  1030. log.log([targetMid, null, message.type, 'Received offer from peer. ' +
  1031. 'Session description:'], message.sdp);
  1032. var offer = new window.RTCSessionDescription({
  1033. type: message.type,
  1034. sdp: message.sdp
  1035. });
  1036. log.log([targetMid, 'RTCSessionDescription', message.type,
  1037. 'Session description object created'], offer);
  1038.  
  1039. // Configure it to force TURN connections by removing non-"relay" candidates
  1040. if (self._forceTURN && !self._enableIceTrickle) {
  1041. if (!self._hasMCU) {
  1042. log.warn([targetMid, 'RTCICECandidate', null, 'Removing non-"relay" candidates from offer ' +
  1043. ' as TURN connections is forced']);
  1044.  
  1045. offer.sdp = offer.sdp.replace(/a=candidate:(?!.*relay.*).*\r\n/g, '');
  1046.  
  1047. } else {
  1048. log.warn([targetMid, 'RTCICECandidate', null, 'Not removing non-"relay"' +
  1049. '" candidates although TURN connections is forced as MCU is present']);
  1050. }
  1051. }
  1052.  
  1053. // This is always the initial state. or even after negotiation is successful
  1054. if (pc.signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
  1055. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1056. '"stable" state for re-negotiation. Dropping message.'], {
  1057. signalingState: pc.signalingState,
  1058. isRestart: !!message.resend
  1059. });
  1060. return;
  1061. }
  1062.  
  1063. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1064. if (pc.processingRemoteSDP) {
  1065. log.warn([targetMid, 'RTCSessionDescription', 'offer',
  1066. 'Dropping of setting local offer as there is another ' +
  1067. 'sessionDescription being processed ->'], offer);
  1068. return;
  1069. }
  1070.  
  1071. pc.processingRemoteSDP = true;
  1072.  
  1073. pc.setRemoteDescription(offer, function() {
  1074. log.debug([targetMid, 'RTCSessionDescription', message.type, 'Remote description set']);
  1075. pc.setOffer = 'remote';
  1076. pc.processingRemoteSDP = false;
  1077. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.OFFER, targetMid);
  1078. self._addIceCandidateFromQueue(targetMid);
  1079. self._doAnswer(targetMid);
  1080. }, function(error) {
  1081. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1082.  
  1083. pc.processingRemoteSDP = false;
  1084.  
  1085. log.error([targetMid, null, message.type, 'Failed setting remote description:'], error);
  1086. });
  1087. };
  1088.  
  1089.  
  1090. /**
  1091. * Function that handles the "candidate" socket message received.
  1092. * See confluence docs for the "candidate" expected properties to be received
  1093. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1094. * @method _candidateHandler
  1095. * @private
  1096. * @for Skylink
  1097. * @since 0.5.1
  1098. */
  1099. Skylink.prototype._candidateHandler = function(message) {
  1100. var targetMid = message.mid;
  1101. var pc = this._peerConnections[targetMid];
  1102. log.log([targetMid, null, message.type, 'Received candidate from peer. Candidate config:'], {
  1103. sdp: message.sdp,
  1104. target: message.target,
  1105. candidate: message.candidate,
  1106. label: message.label
  1107. });
  1108. // create ice candidate object
  1109. var messageCan = message.candidate.split(' ');
  1110. var canType = messageCan[7];
  1111. log.log([targetMid, null, message.type, 'Candidate type:'], canType);
  1112. // if (canType !== 'relay' && canType !== 'srflx') {
  1113. // trace('Skipping non relay and non srflx candidates.');
  1114. var index = message.label;
  1115. var candidate = new window.RTCIceCandidate({
  1116. sdpMLineIndex: index,
  1117. candidate: message.candidate,
  1118. //id: message.id,
  1119. sdpMid: message.id
  1120. //label: index
  1121. });
  1122.  
  1123. if (this._forceTURN && canType !== 'relay') {
  1124. if (!this._hasMCU) {
  1125. log.warn([targetMid, 'RTCICECandidate', null, 'Ignoring adding of "' + canType +
  1126. '" candidate as TURN connections is forced'], candidate);
  1127. return;
  1128. }
  1129.  
  1130. log.warn([targetMid, 'RTCICECandidate', null, 'Not ignoring adding of "' + canType +
  1131. '" candidate although TURN connections is forced as MCU is present'], candidate);
  1132. }
  1133.  
  1134. if (pc) {
  1135. if (pc.signalingState === this.PEER_CONNECTION_STATE.CLOSED) {
  1136. log.warn([targetMid, null, message.type, 'Peer connection state ' +
  1137. 'is closed. Not adding candidate'], candidate);
  1138. return;
  1139. }
  1140. /*if (pc.iceConnectionState === this.ICE_CONNECTION_STATE.CONNECTED) {
  1141. log.debug([targetMid, null, null,
  1142. 'Received but not adding Candidate as we are already connected to this peer']);
  1143. return;
  1144. }*/
  1145. // set queue before ice candidate cannot be added before setRemoteDescription.
  1146. // this will cause a black screen of media stream
  1147. if ((pc.setOffer === 'local' && pc.setAnswer === 'remote') ||
  1148. (pc.setAnswer === 'local' && pc.setOffer === 'remote')) {
  1149. pc.addIceCandidate(candidate, this._onAddIceCandidateSuccess, this._onAddIceCandidateFailure);
  1150. // NOTE ALEX: not implemented in chrome yet, need to wait
  1151. // function () { trace('ICE - addIceCandidate Succesfull. '); },
  1152. // function (error) { trace('ICE - AddIceCandidate Failed: ' + error); }
  1153. //);
  1154. log.debug([targetMid, 'RTCIceCandidate', message.type,
  1155. 'Added candidate'], candidate);
  1156. } else {
  1157. this._addIceCandidateToQueue(targetMid, candidate);
  1158. }
  1159. } else {
  1160. // Added ice candidate to queue because it may be received before sending the offer
  1161. log.debug([targetMid, 'RTCIceCandidate', message.type,
  1162. 'Not adding candidate as peer connection not present'], candidate);
  1163. // NOTE ALEX: if the offer was slow, this can happen
  1164. // we might keep a buffer of candidates to replay after receiving an offer.
  1165. this._addIceCandidateToQueue(targetMid, candidate);
  1166. }
  1167.  
  1168. if (!this._gatheredCandidates[targetMid]) {
  1169. this._gatheredCandidates[targetMid] = {
  1170. sending: { host: [], srflx: [], relay: [] },
  1171. receiving: { host: [], srflx: [], relay: [] }
  1172. };
  1173. }
  1174.  
  1175. this._gatheredCandidates[targetMid].receiving[canType].push({
  1176. sdpMid: candidate.sdpMid,
  1177. sdpMLineIndex: candidate.sdpMLineIndex,
  1178. candidate: candidate.candidate
  1179. });
  1180. };
  1181.  
  1182. /**
  1183. * Function that handles the "answer" socket message received.
  1184. * See confluence docs for the "answer" expected properties to be received
  1185. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1186. * @method _answerHandler
  1187. * @private
  1188. * @for Skylink
  1189. * @since 0.5.1
  1190. */
  1191. Skylink.prototype._answerHandler = function(message) {
  1192. var self = this;
  1193. var targetMid = message.mid;
  1194.  
  1195. log.log([targetMid, null, message.type,
  1196. 'Received answer from peer. Session description:'], message.sdp);
  1197.  
  1198. var pc = self._peerConnections[targetMid];
  1199.  
  1200. if (!pc) {
  1201. log.error([targetMid, null, message.type, 'Peer connection object ' +
  1202. 'not found. Unable to setRemoteDescription for answer']);
  1203. return;
  1204. }
  1205.  
  1206. // Add-on by Web SDK fixes
  1207. if (message.userInfo && typeof message.userInfo === 'object') {
  1208. self._peerInformations[targetMid].settings = message.userInfo.settings;
  1209. self._peerInformations[targetMid].mediaStatus = message.userInfo.mediaStatus;
  1210. self._peerInformations[targetMid].userData = message.userInfo.userData;
  1211. }
  1212.  
  1213. var answer = new window.RTCSessionDescription({
  1214. type: message.type,
  1215. sdp: message.sdp
  1216. });
  1217.  
  1218. log.log([targetMid, 'RTCSessionDescription', message.type,
  1219. 'Session description object created'], answer);
  1220.  
  1221. /*if (pc.remoteDescription ? !!pc.remoteDescription.sdp : false) {
  1222. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1223. pc.remoteDescription);
  1224. return;
  1225. }
  1226.  
  1227. if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  1228. log.error([targetMid, null, message.type, 'Unable to set peer connection ' +
  1229. 'at signalingState "stable". Ignoring remote answer'], pc.signalingState);
  1230. return;
  1231. }*/
  1232.  
  1233. // if firefox and peer is mcu, replace the sdp to suit mcu needs
  1234. if (window.webrtcDetectedType === 'moz' && targetMid === 'MCU') {
  1235. answer.sdp = answer.sdp.replace(/ generation 0/g, '');
  1236. answer.sdp = answer.sdp.replace(/ udp /g, ' UDP ');
  1237. }
  1238.  
  1239. // Configure it to force TURN connections by removing non-"relay" candidates
  1240. if (self._forceTURN && !self._enableIceTrickle) {
  1241. if (!self._hasMCU) {
  1242. log.warn([targetMid, 'RTCICECandidate', null, 'Removing non-"relay" candidates from answer ' +
  1243. ' as TURN connections is forced']);
  1244.  
  1245. answer.sdp = answer.sdp.replace(/a=candidate:(?!.*relay.*).*\r\n/g, '');
  1246.  
  1247. } else {
  1248. log.warn([targetMid, 'RTCICECandidate', null, 'Not removing non-"relay"' +
  1249. '" candidates although TURN connections is forced as MCU is present']);
  1250. }
  1251. }
  1252.  
  1253. // This should be the state after offer is received. or even after negotiation is successful
  1254. if (pc.signalingState !== self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
  1255. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1256. '"have-local-offer" state for re-negotiation. Dropping message.'], {
  1257. signalingState: pc.signalingState,
  1258. isRestart: !!message.restart
  1259. });
  1260. return;
  1261. }
  1262.  
  1263. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1264. if (pc.processingRemoteSDP) {
  1265. log.warn([targetMid, 'RTCSessionDescription', 'answer',
  1266. 'Dropping of setting local answer as there is another ' +
  1267. 'sessionDescription being processed ->'], answer);
  1268. return;
  1269. }
  1270.  
  1271. pc.processingRemoteSDP = true;
  1272.  
  1273. pc.setRemoteDescription(answer, function() {
  1274. log.debug([targetMid, null, message.type, 'Remote description set']);
  1275. pc.setAnswer = 'remote';
  1276. pc.processingRemoteSDP = false;
  1277. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ANSWER, targetMid);
  1278. self._addIceCandidateFromQueue(targetMid);
  1279.  
  1280. }, function(error) {
  1281. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1282.  
  1283. pc.processingRemoteSDP = false;
  1284.  
  1285. log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
  1286. error: error,
  1287. state: pc.signalingState
  1288. });
  1289. });
  1290. };
  1291.