SkylinkJS 0.6.13

File: source/data-channel.js

  1. /**
  2. * These are the list of DataChannel connection states that Skylink would trigger.
  3. * - Some of the state references the [w3c WebRTC Specification Draft](http://w3c.github.io/webrtc-pc/#idl-def-RTCDataChannelState),
  4. * except the <code>ERROR</code> state, which is an addition provided state by Skylink
  5. * to inform exception during the DataChannel connection with Peers.
  6. * @attribute DATA_CHANNEL_STATE
  7. * @type JSON
  8. * @param {String} CONNECTING <small>Value <code>"connecting"</code></small>
  9. * The state when DataChannel is attempting to establish a connection.<br>
  10. * This is the initial state when a DataChannel connection is created.
  11. * @param {String} OPEN <small>Value <code>"open"</code></small>
  12. * The state when DataChannel connection is established.<br>
  13. * This happens usually after <code>CONNECTING</code> state, or not when DataChannel connection
  14. * is from initializing Peer (the one who begins the DataChannel connection).
  15. * @param {String} CLOSING <small>Value <code>"closing"</code></small>
  16. * The state when DataChannel connection is closing.<br>
  17. * This happens when DataChannel connection is closing and happens after <code>OPEN</code>.
  18. * @param {String} CLOSED <small>Value <code>"closed"</code></small>
  19. * The state when DataChannel connection is closed.<br>
  20. * This happens when DataChannel connection has closed and happens after <code>CLOSING</code>
  21. * (or sometimes <code>OPEN</code> depending on the browser implementation).
  22. * @param {String} ERROR <small>Value <code>"error"</code></small>
  23. * The state when DataChannel connection have met with an exception.<br>
  24. * This may happen during any state not after <code>CLOSED</code>.
  25. * @readOnly
  26. * @component DataChannel
  27. * @for Skylink
  28. * @since 0.1.0
  29. */
  30. Skylink.prototype.DATA_CHANNEL_STATE = {
  31. CONNECTING: 'connecting',
  32. OPEN: 'open',
  33. CLOSING: 'closing',
  34. CLOSED: 'closed',
  35. ERROR: 'error'
  36. };
  37.  
  38. /**
  39. * These are the types of DataChannel connection that Skylink provides.
  40. * - Different channels serves different functionalities.
  41. * @attribute DATA_CHANNEL_TYPE
  42. * @type JSON
  43. * @param {String} MESSAGING <small><b>MAIN connection</b> | Value <code>"messaging"</code></small>
  44. * This DataChannel connection is used for P2P messaging only, as used in
  45. * {{#crossLink "Skylink/sendP2PMessage:method"}}sendP2PMessage(){{/crossLink}}.<br>
  46. * Unless if self connects with Peers connecting from the mobile SDK platform applications,
  47. * this connection would be used for data transfers as used in
  48. * {{#crossLink "Skylink/sendBlobData:method"}}sendBlobData(){{/crossLink}} and
  49. * and {{#crossLink "Skylink/sendURLData:method"}}sendURLData(){{/crossLink}}, which allows
  50. * only one outgoing and incoming data transfer one at a time (no multi-transfer support).<br>
  51. * This connection will always be kept alive until the Peer connection has ended.
  52. * @param {String} DATA <small>Value <code>"data"</code></small>
  53. * This DataChannel connection is used for a data transfer, as used in
  54. * {{#crossLink "Skylink/sendBlobData:method"}}sendBlobData(){{/crossLink}}
  55. * and {{#crossLink "Skylink/sendURLData:method"}}sendURLData(){{/crossLink}}.<br>
  56. * If self connects with Peers with DataChannel connections of this type,
  57. * it indicates that multi-transfer is supported.<br>
  58. * This connection will be closed once the data transfer has completed or terminated.
  59. * @readOnly
  60. * @component DataChannel
  61. * @for Skylink
  62. * @since 0.6.1
  63. */
  64. Skylink.prototype.DATA_CHANNEL_TYPE = {
  65. MESSAGING: 'messaging',
  66. DATA: 'data'
  67. };
  68.  
  69. /**
  70. * The flag that indicates if Peers connection should have any
  71. * DataChannel connections.
  72. * @attribute _enableDataChannel
  73. * @type Boolean
  74. * @default true
  75. * @private
  76. * @component DataChannel
  77. * @for Skylink
  78. * @since 0.3.0
  79. */
  80. Skylink.prototype._enableDataChannel = true;
  81.  
  82. /**
  83. * Stores the list of DataChannel connections.
  84. * @attribute _dataChannels
  85. * @param {Array} (#peerId) The Peer ID associated with the list of
  86. * DataChannel connections.
  87. * @param {Object} (#peerId).main The DataChannel connection object
  88. * that is used for messaging only associated with the Peer connection.
  89. * This is the sole channel for sending P2P messages in
  90. * {{#crossLink "Skylink/sendP2PMessage:method"}}sendP2PMessage(){{/crossLink}}.
  91. * This connection will always be kept alive until the Peer connection has
  92. * ended. The <code>channelName</code> for this reserved key is <code>"main"</code>.
  93. * @param {Object} (#peerId).(#channelName) The DataChannel connection
  94. * object that is used temporarily for a data transfer associated with the
  95. * Peer connection. This is using caused by methods
  96. * {{#crossLink "Skylink/sendBlobData:method"}}sendBlobData(){{/crossLink}}
  97. * and {{#crossLink "Skylink/sendURLData:method"}}sendURLData(){{/crossLink}}.
  98. * This connection will be closed once the transfer has completed or terminated.
  99. * The <code>channelName</code> is usually the data transfer ID.
  100. * @type JSON
  101. * @private
  102. * @component DataChannel
  103. * @for Skylink
  104. * @since 0.2.0
  105. */
  106. Skylink.prototype._dataChannels = {};
  107.  
  108. /**
  109. * Starts a DataChannel connection with a Peer connection. If the
  110. * DataChannel is provided in the parameter, it simply appends
  111. * event handlers to check the current state of the DataChannel.
  112. * @method _createDataChannel
  113. * @param {String} peerId The Peer ID to start the
  114. * DataChannel with or associate the provided DataChannel object
  115. * connection with.
  116. * @param {String} channelType The DataChannel functionality type.
  117. * [Rel: Skylink.DATA_CHANNEL_TYPE]
  118. * @param {Object} [dataChannel] The RTCDataChannel object received
  119. * in the Peer connection <code>.ondatachannel</code> event.
  120. * @param {String} customChannelName The custom RTCDataChannel label
  121. * name to identify the different opened channels.
  122. * @trigger dataChannelState
  123. * @return {Object} The DataChannel connection object associated with
  124. * the provided Peer ID.
  125. * @private
  126. * @component DataChannel
  127. * @for Skylink
  128. * @since 0.5.5
  129. */
  130. Skylink.prototype._createDataChannel = function(peerId, channelType, dc, customChannelName) {
  131. var self = this;
  132.  
  133. if (typeof dc === 'string') {
  134. customChannelName = dc;
  135. dc = null;
  136. }
  137.  
  138. if (!customChannelName) {
  139. log.error([peerId, 'RTCDataChannel', null, 'Aborting of creating Datachannel as no ' +
  140. 'channel name is provided for channel. Aborting of creating Datachannel'], {
  141. channelType: channelType
  142. });
  143. return;
  144. }
  145.  
  146. var channelName = (dc) ? dc.label : customChannelName;
  147. var pc = self._peerConnections[peerId];
  148.  
  149. var SctpSupported =
  150. !(window.webrtcDetectedBrowser === 'chrome' && window.webrtcDetectedVersion < 30 ||
  151. window.webrtcDetectedBrowser === 'opera' && window.webrtcDetectedVersion < 20 );
  152.  
  153. if (!SctpSupported) {
  154. log.warn([peerId, 'RTCDataChannel', channelName, 'SCTP not supported'], {
  155. channelType: channelType
  156. });
  157. return;
  158. }
  159.  
  160. var dcHasOpened = function () {
  161. log.log([peerId, 'RTCDataChannel', channelName, 'Datachannel state ->'], {
  162. readyState: 'open',
  163. channelType: channelType
  164. });
  165.  
  166. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.OPEN,
  167. peerId, null, channelName, channelType);
  168. };
  169.  
  170. if (!dc) {
  171. try {
  172. dc = pc.createDataChannel(channelName);
  173.  
  174. if (dc.readyState === self.DATA_CHANNEL_STATE.OPEN) {
  175. // the datachannel was not defined in array before it was triggered
  176. // set a timeout to allow the dc objec to be returned before triggering "open"
  177. setTimeout(dcHasOpened, 500);
  178. } else {
  179. self._trigger('dataChannelState', dc.readyState, peerId, null,
  180. channelName, channelType);
  181.  
  182. self._wait(function () {
  183. log.log([peerId, 'RTCDataChannel', dc.label, 'Firing callback. ' +
  184. 'Datachannel state has opened ->'], dc.readyState);
  185. dcHasOpened();
  186. }, function () {
  187. return dc.readyState === self.DATA_CHANNEL_STATE.OPEN;
  188. });
  189. }
  190.  
  191. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel RTC object is created'], {
  192. readyState: dc.readyState,
  193. channelType: channelType
  194. });
  195.  
  196. } catch (error) {
  197. log.error([peerId, 'RTCDataChannel', channelName, 'Exception occurred in datachannel:'], {
  198. channelType: channelType,
  199. error: error
  200. });
  201. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error,
  202. channelName, channelType);
  203. return;
  204. }
  205. } else {
  206. if (dc.readyState === self.DATA_CHANNEL_STATE.OPEN) {
  207. // the datachannel was not defined in array before it was triggered
  208. // set a timeout to allow the dc objec to be returned before triggering "open"
  209. setTimeout(dcHasOpened, 500);
  210. } else {
  211. dc.onopen = dcHasOpened;
  212. }
  213. }
  214.  
  215. log.log([peerId, 'RTCDataChannel', channelName, 'Binary type support ->'], {
  216. binaryType: dc.binaryType,
  217. readyState: dc.readyState,
  218. channelType: channelType
  219. });
  220.  
  221. dc.dcType = channelType;
  222.  
  223. dc.onerror = function(error) {
  224. log.error([peerId, 'RTCDataChannel', channelName, 'Exception occurred in datachannel:'], {
  225. channelType: channelType,
  226. readyState: dc.readyState,
  227. error: error
  228. });
  229. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error,
  230. channelName, channelType);
  231. };
  232.  
  233. dc.onclose = function() {
  234. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel state ->'], {
  235. readyState: 'closed',
  236. channelType: channelType
  237. });
  238.  
  239. dc.hasFiredClosed = true;
  240.  
  241. // give it some time to set the variable before actually closing and checking.
  242. setTimeout(function () {
  243. // redefine pc
  244. pc = self._peerConnections[peerId];
  245. // if closes because of firefox, reopen it again
  246. // if it is closed because of a restart, ignore
  247.  
  248. var checkIfChannelClosedDuringConn = !!pc ? !pc.dataChannelClosed : false;
  249.  
  250. if (checkIfChannelClosedDuringConn && dc.dcType === self.DATA_CHANNEL_TYPE.MESSAGING) {
  251. log.debug([peerId, 'RTCDataChannel', channelName, 'Re-opening closed datachannel in ' +
  252. 'on-going connection'], {
  253. channelType: channelType,
  254. readyState: dc.readyState,
  255. isClosedDuringConnection: checkIfChannelClosedDuringConn
  256. });
  257.  
  258. self._dataChannels[peerId].main =
  259. self._createDataChannel(peerId, self.DATA_CHANNEL_TYPE.MESSAGING, null, peerId);
  260.  
  261. log.debug([peerId, 'RTCDataChannel', channelName, 'Re-opened closed datachannel'], {
  262. channelType: channelType,
  263. readyState: dc.readyState,
  264. isClosedDuringConnection: checkIfChannelClosedDuringConn
  265. });
  266.  
  267. } else {
  268. self._closeDataChannel(peerId, channelName);
  269. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSED, peerId, null,
  270. channelName, channelType);
  271.  
  272. log.debug([peerId, 'RTCDataChannel', channelName, 'Datachannel has closed'], {
  273. channelType: channelType,
  274. readyState: dc.readyState,
  275. isClosedDuringConnection: checkIfChannelClosedDuringConn
  276. });
  277. }
  278. }, 100);
  279. };
  280.  
  281. dc.onmessage = function(event) {
  282. self._dataChannelProtocolHandler(event.data, peerId, channelName, channelType);
  283. };
  284.  
  285. return dc;
  286. };
  287.  
  288. /**
  289. * Sends data over the DataChannel connection associated
  290. * with the Peer connection.
  291. * The current supported data type is <code>string</code>. <code>Blob</code>,
  292. * <code>ArrayBuffer</code> types support is not yet currently handled or
  293. * implemented.
  294. * @method _sendDataChannelMessage
  295. * @param {String} peerId The Peer ID to send the data to the
  296. * associated DataChannel connection.
  297. * @param {JSON|String} data The data to send over. <code>string</code> is only
  298. * used to send binary data string over. <code>JSON</code> is primarily used
  299. * for the {{#crossLink "Skylink/DT_PROTOCOL_VERSION:attribute"}}DT Protocol{{/crossLink}}
  300. * that Skylink follows for P2P messaging and transfers.
  301. * @param {String} [channelName="main"] The DataChannel channelName of the connection
  302. * to send the data over to. The datachannel to send messages to. By default,
  303. * if the DataChannel <code>channelName</code> is not provided,
  304. * the DataChannel connection associated with the channelName <code>"main"</code> would be used.
  305. * @trigger dataChannelState
  306. * @private
  307. * @component DataChannel
  308. * @for Skylink
  309. * @since 0.5.2
  310. */
  311. Skylink.prototype._sendDataChannelMessage = function(peerId, data, channelKey) {
  312. var self = this;
  313.  
  314. var channelName;
  315.  
  316. if (!channelKey || channelKey === peerId) {
  317. channelKey = 'main';
  318. }
  319.  
  320. var dcList = self._dataChannels[peerId] || {};
  321. var dc = dcList[channelKey];
  322.  
  323. if (!dc) {
  324. log.error([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  325. 'Datachannel connection to peer does not exist'], {
  326. enabledState: self._enableDataChannel,
  327. dcList: dcList,
  328. dc: dc,
  329. type: (data.type || 'DATA'),
  330. data: data,
  331. channelKey: channelKey
  332. });
  333. return;
  334. } else {
  335. channelName = dc.label;
  336.  
  337. log.debug([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  338. 'Sending data using this channel key'], data);
  339.  
  340. if (dc.readyState === this.DATA_CHANNEL_STATE.OPEN) {
  341. var dataString = (typeof data === 'object') ? JSON.stringify(data) : data;
  342. log.debug([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  343. 'Sending to peer ->'], {
  344. readyState: dc.readyState,
  345. type: (data.type || 'DATA'),
  346. data: data
  347. });
  348. dc.send(dataString);
  349. } else {
  350. log.error([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  351. 'Datachannel is not opened'], {
  352. readyState: dc.readyState,
  353. type: (data.type || 'DATA'),
  354. data: data
  355. });
  356. this._trigger('dataChannelState', this.DATA_CHANNEL_STATE.ERROR,
  357. peerId, 'Datachannel is not ready.\nState is: ' + dc.readyState);
  358. }
  359. }
  360. };
  361.  
  362. /**
  363. * Stops DataChannel connections associated with a Peer connection
  364. * and remove any object references to the DataChannel connection(s).
  365. * @method _closeDataChannel
  366. * @param {String} peerId The Peer ID associated with the DataChannel
  367. * connection(s) to close.
  368. * @param {String} [channelName] The targeted DataChannel <code>channelName</code>
  369. * to close the connection with. If <code>channelName</code> is not provided,
  370. * all associated DataChannel connections with the Peer connection would be closed.
  371. * @trigger dataChannelState
  372. * @private
  373. * @component DataChannel
  374. * @for Skylink
  375. * @since 0.1.0
  376. */
  377. Skylink.prototype._closeDataChannel = function(peerId, channelName) {
  378. var self = this;
  379. var dcList = self._dataChannels[peerId] || {};
  380. var dcKeysList = Object.keys(dcList);
  381.  
  382.  
  383. if (channelName) {
  384. dcKeysList = [channelName];
  385. }
  386.  
  387. for (var i = 0; i < dcKeysList.length; i++) {
  388. var channelKey = dcKeysList[i];
  389. var dc = dcList[channelKey];
  390.  
  391. if (dc) {
  392. if (dc.readyState !== self.DATA_CHANNEL_STATE.CLOSED) {
  393. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  394. 'Closing datachannel']);
  395. dc.close();
  396. } else {
  397. if (!dc.hasFiredClosed && window.webrtcDetectedBrowser === 'firefox') {
  398. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  399. 'Closed Firefox datachannel']);
  400. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSED, peerId,
  401. null, channelName, channelKey === 'main' ? self.DATA_CHANNEL_TYPE.MESSAGING :
  402. self.DATA_CHANNEL_TYPE.DATA);
  403. }
  404. }
  405. delete self._dataChannels[peerId][channelKey];
  406.  
  407. log.log([peerId, 'RTCDataChannel', channelKey + '|' + dc.label,
  408. 'Sucessfully removed datachannel']);
  409. } else {
  410. log.log([peerId, 'RTCDataChannel', channelKey + '|' + channelName,
  411. 'Unable to close Datachannel as it does not exists'], {
  412. dc: dc,
  413. dcList: dcList
  414. });
  415. }
  416. }
  417. };