File: source/data-channel.js

  1. /**
  2. * The list of Datachannel connection states.
  3. * @attribute DATA_CHANNEL_STATE
  4. * @param {String} CONNECTING <small>Value <code>"connecting"</code></small>
  5. * The value of the state when Datachannel is attempting to establish a connection.
  6. * @param {String} OPEN <small>Value <code>"open"</code></small>
  7. * The value of the state when Datachannel has established a connection.
  8. * @param {String} CLOSING <small>Value <code>"closing"</code></small>
  9. * The value of the state when Datachannel connection is closing.
  10. * @param {String} CLOSED <small>Value <code>"closed"</code></small>
  11. * The value of the state when Datachannel connection has closed.
  12. * @param {String} ERROR <small>Value <code>"error"</code></small>
  13. * The value of the state when Datachannel connection has errors.
  14. * @type JSON
  15. * @readOnly
  16. * @for Skylink
  17. * @since 0.1.0
  18. */
  19. Skylink.prototype.DATA_CHANNEL_STATE = {
  20. CONNECTING: 'connecting',
  21. OPEN: 'open',
  22. CLOSING: 'closing',
  23. CLOSED: 'closed',
  24. ERROR: 'error'
  25. };
  26.  
  27. /**
  28. * The list of Datachannel types.
  29. * @attribute DATA_CHANNEL_TYPE
  30. * @param {String} MESSAGING <small>Value <code>"messaging"</code></small>
  31. * The value of the Datachannel type that is used only for messaging in
  32. * <a href="#method_sendP2PMessage"><code>sendP2PMessage()</code> method</a>.
  33. * <small>However for Peers that do not support simultaneous data transfers, this Datachannel
  34. * type will be used to do data transfers (1 at a time).</small>
  35. * <small>Each Peer connections will only have one of this Datachannel type and the
  36. * connection will only close when the Peer connection is closed (happens when <a href="#event_peerConnectionState">
  37. * <code>peerConnectionState</code> event</a> triggers parameter payload <code>state</code> as
  38. * <code>CLOSED</code> for Peer).</small>
  39. * @param {String} DATA <small>Value <code>"data"</code></small>
  40. * The value of the Datachannel type that is used only for a data transfer in
  41. * <a href="#method_sendURLData"><code>sendURLData()</code> method</a> and
  42. * <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a>.
  43. * <small>The connection will close after the data transfer has been completed or terminated (happens when
  44. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter payload
  45. * <code>state</code> as <code>DOWNLOAD_COMPLETED</code>, <code>UPLOAD_COMPLETED</code>,
  46. * <code>REJECTED</code>, <code>CANCEL</code> or <code>ERROR</code> for Peer).</small>
  47. * @type JSON
  48. * @readOnly
  49. * @for Skylink
  50. * @since 0.6.1
  51. */
  52. Skylink.prototype.DATA_CHANNEL_TYPE = {
  53. MESSAGING: 'messaging',
  54. DATA: 'data'
  55. };
  56.  
  57. /**
  58. * Stores the flag if Peers should have any Datachannel connections.
  59. * @attribute _enableDataChannel
  60. * @default true
  61. * @type Boolean
  62. * @private
  63. * @for Skylink
  64. * @since 0.3.0
  65. */
  66. Skylink.prototype._enableDataChannel = true;
  67.  
  68. /**
  69. * Stores the list of Peer Datachannel connections.
  70. * @attribute _dataChannels
  71. * @param {JSON} (#peerId) The list of Datachannels associated with Peer ID.
  72. * @param {RTCDataChannel} (#peerId).<#channelLabel> The Datachannel connection.
  73. * The property name <code>"main"</code> is reserved for messaging Datachannel type.
  74. * @type JSON
  75. * @private
  76. * @for Skylink
  77. * @since 0.2.0
  78. */
  79. Skylink.prototype._dataChannels = {};
  80.  
  81. /**
  82. * Function that starts a Datachannel connection with Peer.
  83. * @method _createDataChannel
  84. * @private
  85. * @for Skylink
  86. * @since 0.5.5
  87. */
  88. Skylink.prototype._createDataChannel = function(peerId, channelType, dc, customChannelName) {
  89. var self = this;
  90.  
  91. if (typeof dc === 'string') {
  92. customChannelName = dc;
  93. dc = null;
  94. }
  95.  
  96. if (!customChannelName) {
  97. log.error([peerId, 'RTCDataChannel', null, 'Aborting of creating Datachannel as no ' +
  98. 'channel name is provided for channel. Aborting of creating Datachannel'], {
  99. channelType: channelType
  100. });
  101. return;
  102. }
  103.  
  104. var channelName = (dc) ? dc.label : customChannelName;
  105. var pc = self._peerConnections[peerId];
  106.  
  107. var SctpSupported =
  108. !(window.webrtcDetectedBrowser === 'chrome' && window.webrtcDetectedVersion < 30 ||
  109. window.webrtcDetectedBrowser === 'opera' && window.webrtcDetectedVersion < 20 );
  110.  
  111. if (!SctpSupported) {
  112. log.warn([peerId, 'RTCDataChannel', channelName, 'SCTP not supported'], {
  113. channelType: channelType
  114. });
  115. return;
  116. }
  117.  
  118. var dcHasOpened = function () {
  119. log.log([peerId, 'RTCDataChannel', channelName, 'Datachannel state ->'], {
  120. readyState: 'open',
  121. channelType: channelType
  122. });
  123.  
  124. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.OPEN,
  125. peerId, null, channelName, channelType);
  126. };
  127.  
  128. if (!dc) {
  129. try {
  130. dc = pc.createDataChannel(channelName);
  131.  
  132. if (dc.readyState === self.DATA_CHANNEL_STATE.OPEN) {
  133. // the datachannel was not defined in array before it was triggered
  134. // set a timeout to allow the dc objec to be returned before triggering "open"
  135. setTimeout(dcHasOpened, 500);
  136. } else {
  137. self._trigger('dataChannelState', dc.readyState, peerId, null,
  138. channelName, channelType);
  139.  
  140. self._wait(function () {
  141. log.log([peerId, 'RTCDataChannel', dc.label, 'Firing callback. ' +
  142. 'Datachannel state has opened ->'], dc.readyState);
  143. dcHasOpened();
  144. }, function () {
  145. return dc.readyState === self.DATA_CHANNEL_STATE.OPEN;
  146. });
  147. }
  148.  
  149. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel RTC object is created'], {
  150. readyState: dc.readyState,
  151. channelType: channelType
  152. });
  153.  
  154. } catch (error) {
  155. log.error([peerId, 'RTCDataChannel', channelName, 'Exception occurred in datachannel:'], {
  156. channelType: channelType,
  157. error: error
  158. });
  159. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error,
  160. channelName, channelType);
  161. return;
  162. }
  163. } else {
  164. if (dc.readyState === self.DATA_CHANNEL_STATE.OPEN) {
  165. // the datachannel was not defined in array before it was triggered
  166. // set a timeout to allow the dc objec to be returned before triggering "open"
  167. setTimeout(dcHasOpened, 500);
  168. } else {
  169. dc.onopen = dcHasOpened;
  170. }
  171. }
  172.  
  173. log.log([peerId, 'RTCDataChannel', channelName, 'Binary type support ->'], {
  174. binaryType: dc.binaryType,
  175. readyState: dc.readyState,
  176. channelType: channelType
  177. });
  178.  
  179. dc.dcType = channelType;
  180.  
  181. dc.onerror = function(error) {
  182. log.error([peerId, 'RTCDataChannel', channelName, 'Exception occurred in datachannel:'], {
  183. channelType: channelType,
  184. readyState: dc.readyState,
  185. error: error
  186. });
  187. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error,
  188. channelName, channelType);
  189. };
  190.  
  191. dc.onclose = function() {
  192. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel state ->'], {
  193. readyState: 'closed',
  194. channelType: channelType
  195. });
  196.  
  197. dc.hasFiredClosed = true;
  198.  
  199. // give it some time to set the variable before actually closing and checking.
  200. setTimeout(function () {
  201. // redefine pc
  202. pc = self._peerConnections[peerId];
  203. // if closes because of firefox, reopen it again
  204. // if it is closed because of a restart, ignore
  205.  
  206. var checkIfChannelClosedDuringConn = !!pc ? !pc.dataChannelClosed : false;
  207.  
  208. if (checkIfChannelClosedDuringConn && dc.dcType === self.DATA_CHANNEL_TYPE.MESSAGING) {
  209. log.debug([peerId, 'RTCDataChannel', channelName, 'Re-opening closed datachannel in ' +
  210. 'on-going connection'], {
  211. channelType: channelType,
  212. readyState: dc.readyState,
  213. isClosedDuringConnection: checkIfChannelClosedDuringConn
  214. });
  215.  
  216. self._dataChannels[peerId].main =
  217. self._createDataChannel(peerId, self.DATA_CHANNEL_TYPE.MESSAGING, null, peerId);
  218.  
  219. log.debug([peerId, 'RTCDataChannel', channelName, 'Re-opened closed datachannel'], {
  220. channelType: channelType,
  221. readyState: dc.readyState,
  222. isClosedDuringConnection: checkIfChannelClosedDuringConn
  223. });
  224.  
  225. } else {
  226. self._closeDataChannel(peerId, channelName);
  227. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSED, peerId, null,
  228. channelName, channelType);
  229.  
  230. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel has closed'], {
  231. channelType: channelType,
  232. readyState: dc.readyState,
  233. isClosedDuringConnection: checkIfChannelClosedDuringConn
  234. });
  235. }
  236. }, 100);
  237. };
  238.  
  239. dc.onmessage = function(event) {
  240. self._dataChannelProtocolHandler(event.data, peerId, channelName, channelType);
  241. };
  242.  
  243. return dc;
  244. };
  245.  
  246. /**
  247. * Function that sends data over the Datachannel connection.
  248. * @method _sendDataChannelMessage
  249. * @private
  250. * @for Skylink
  251. * @since 0.5.2
  252. */
  253. Skylink.prototype._sendDataChannelMessage = function(peerId, data, channelKey) {
  254. var self = this;
  255.  
  256. var channelName;
  257.  
  258. if (!channelKey || channelKey === peerId) {
  259. channelKey = 'main';
  260. }
  261.  
  262. var dcList = self._dataChannels[peerId] || {};
  263. var dc = dcList[channelKey];
  264.  
  265. if (!dc) {
  266. log.error([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  267. 'Datachannel connection to peer does not exist'], {
  268. enabledState: self._enableDataChannel,
  269. dcList: dcList,
  270. dc: dc,
  271. type: (data.type || 'DATA'),
  272. data: data,
  273. channelKey: channelKey
  274. });
  275. return;
  276. } else {
  277. channelName = dc.label;
  278.  
  279. log.debug([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  280. 'Sending data using this channel key'], data);
  281.  
  282. if (dc.readyState === this.DATA_CHANNEL_STATE.OPEN) {
  283. var dataString = (typeof data === 'object') ? JSON.stringify(data) : data;
  284. log.debug([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  285. 'Sending to peer ->'], {
  286. readyState: dc.readyState,
  287. type: (data.type || 'DATA'),
  288. data: data
  289. });
  290. dc.send(dataString);
  291. } else {
  292. log.error([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  293. 'Datachannel is not opened'], {
  294. readyState: dc.readyState,
  295. type: (data.type || 'DATA'),
  296. data: data
  297. });
  298. this._trigger('dataChannelState', this.DATA_CHANNEL_STATE.ERROR,
  299. peerId, 'Datachannel is not ready.\nState is: ' + dc.readyState);
  300. }
  301. }
  302. };
  303.  
  304. /**
  305. * Function that stops the Datachannel connection and removes object references.
  306. * @method _closeDataChannel
  307. * @private
  308. * @for Skylink
  309. * @since 0.1.0
  310. */
  311. Skylink.prototype._closeDataChannel = function(peerId, channelName) {
  312. var self = this;
  313. var dcList = self._dataChannels[peerId] || {};
  314. var dcKeysList = Object.keys(dcList);
  315.  
  316.  
  317. if (channelName) {
  318. dcKeysList = [channelName];
  319. }
  320.  
  321. for (var i = 0; i < dcKeysList.length; i++) {
  322. var channelKey = dcKeysList[i];
  323. var dc = dcList[channelKey];
  324.  
  325. if (dc) {
  326. if (dc.readyState !== self.DATA_CHANNEL_STATE.CLOSED) {
  327. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  328. 'Closing datachannel']);
  329. dc.close();
  330. } else {
  331. if (!dc.hasFiredClosed && window.webrtcDetectedBrowser === 'firefox') {
  332. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  333. 'Closed Firefox datachannel']);
  334. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSED, peerId,
  335. null, channelName, channelKey === 'main' ? self.DATA_CHANNEL_TYPE.MESSAGING :
  336. self.DATA_CHANNEL_TYPE.DATA);
  337. }
  338. }
  339. delete self._dataChannels[peerId][channelKey];
  340.  
  341. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  342. 'Sucessfully removed datachannel']);
  343. } else {
  344. log.log([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  345. 'Unable to close Datachannel as it does not exists'], {
  346. dc: dc,
  347. dcList: dcList
  348. });
  349. }
  350. }
  351. };