File: source/data-channel.js

  1. /**
  2. * Function that starts a Datachannel connection with Peer.
  3. * @method _createDataChannel
  4. * @private
  5. * @for Skylink
  6. * @since 0.5.5
  7. */
  8. Skylink.prototype._createDataChannel = function(peerId, dataChannel, bufferThreshold, createAsMessagingChannel) {
  9. var self = this;
  10. var channelName = (self._user && self._user.sid ? self._user.sid : '-') + '_' + peerId;
  11. var channelType = createAsMessagingChannel ? self.DATA_CHANNEL_TYPE.MESSAGING : self.DATA_CHANNEL_TYPE.DATA;
  12. var channelProp = channelType === self.DATA_CHANNEL_TYPE.MESSAGING ? 'main' : channelName;
  13.  
  14. if (!self._user) {
  15. log.error([peerId, 'RTCDataChannel', channelProp,
  16. 'Aborting of creating or initializing Datachannel as User does not have Room session']);
  17. return;
  18. }
  19.  
  20. if (!(self._peerConnections[peerId] &&
  21. self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.CLOSED)) {
  22. log.error([peerId, 'RTCDataChannel', channelProp,
  23. 'Aborting of creating or initializing Datachannel as Peer connection does not exists']);
  24. return;
  25. }
  26.  
  27.  
  28. if (dataChannel && typeof dataChannel === 'object') {
  29. channelName = dataChannel.label;
  30.  
  31. } else if (typeof dataChannel === 'string') {
  32. channelName = dataChannel;
  33. dataChannel = null;
  34. }
  35.  
  36. if (!self._dataChannels[peerId]) {
  37. channelProp = 'main';
  38. channelType = self.DATA_CHANNEL_TYPE.MESSAGING;
  39. self._dataChannels[peerId] = {};
  40. log.debug([peerId, 'RTCDataChannel', channelProp, 'initializing main DataChannel']);
  41. } else if (self._dataChannels[peerId].main && self._dataChannels[peerId].main.channel.label === channelName) {
  42. channelProp = 'main';
  43. channelType = self.DATA_CHANNEL_TYPE.MESSAGING;
  44. }
  45.  
  46. if (!dataChannel) {
  47. try {
  48. dataChannel = self._peerConnections[peerId].createDataChannel(channelName, {
  49. reliable: true,
  50. ordered: true
  51. });
  52.  
  53. } catch (error) {
  54. log.error([peerId, 'RTCDataChannel', channelProp, 'Failed creating Datachannel ->'], error);
  55. self._handleDatachannelStats('error', peerId, { label: channelName }, channelProp, error);
  56. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CREATE_ERROR, peerId, error, channelName,
  57. channelType, null, self._getDataChannelBuffer(dataChannel));
  58. return;
  59. }
  60. }
  61.  
  62. /**
  63. * Subscribe to events
  64. */
  65. dataChannel.onerror = function (evt) {
  66. var channelError = evt.error || evt;
  67.  
  68. log.error([peerId, 'RTCDataChannel', channelProp, 'Datachannel has an exception ->'], channelError);
  69.  
  70. self._handleDatachannelStats('error', peerId, dataChannel, channelProp, channelError);
  71. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, channelError, channelName,
  72. channelType, null, self._getDataChannelBuffer(dataChannel));
  73. };
  74.  
  75. // State where we can start calling .send() to queue more buffered data to be sent
  76. // RTCDataChannel has an internal mechanism to queue data to be sent over
  77. // This event might not be even triggered at all
  78. dataChannel.onbufferedamountlow = function () {
  79. log.debug([peerId, 'RTCDataChannel', channelProp, 'Datachannel buffering data transfer low']);
  80.  
  81. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.BUFFERED_AMOUNT_LOW, peerId, null, channelName,
  82. channelType, null, self._getDataChannelBuffer(dataChannel));
  83. };
  84.  
  85. dataChannel.onmessage = function(event) {
  86. self._processDataChannelData(event.data, peerId, channelName, channelType);
  87. };
  88.  
  89. var onOpenHandlerFn = function () {
  90. log.debug([peerId, 'RTCDataChannel', channelProp, 'Datachannel has opened']);
  91.  
  92. dataChannel.bufferedAmountLowThreshold = bufferThreshold || 0;
  93.  
  94. self._handleDatachannelStats('open', peerId, dataChannel, channelProp);
  95. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.OPEN, peerId, null, channelName,
  96. channelType, null, self._getDataChannelBuffer(dataChannel));
  97. };
  98.  
  99. if (dataChannel.readyState === self.DATA_CHANNEL_STATE.OPEN) {
  100. setTimeout(onOpenHandlerFn, 1); // 500);
  101.  
  102. } else {
  103. self._handleDatachannelStats('connecting', peerId, dataChannel, channelProp);
  104. self._trigger('dataChannelState', dataChannel.readyState, peerId, null, channelName,
  105. channelType, null, self._getDataChannelBuffer(dataChannel));
  106.  
  107. dataChannel.onopen = onOpenHandlerFn;
  108. }
  109.  
  110. var getTransferIDByPeerId = function (pid) {
  111. for (var transferId in self._dataTransfers) {
  112. if (transferId.indexOf(pid) !== -1) {
  113. return transferId;
  114. }
  115. }
  116. return null;
  117. }
  118.  
  119. var onCloseHandlerFn = function () {
  120. var dcMessageStr = "Datachannel has closed";
  121. var transferId = getTransferIDByPeerId(peerId);
  122. log.debug([peerId, 'RTCDataChannel', channelProp, dcMessageStr]);
  123.  
  124. self._handleDatachannelStats('closed', peerId, dataChannel, channelProp);
  125. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSED, peerId, null, channelName,
  126. channelType, null, self._getDataChannelBuffer(dataChannel));
  127.  
  128. // ESS-983 Handling dataChannel unexpected close to trigger dataTransferState Error.
  129. transferId && self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, peerId, self._getTransferInfo(transferId, peerId, true, false, false), new Error(dcMessageStr));
  130.  
  131. if (self._peerConnections[peerId] && self._peerConnections[peerId].remoteDescription &&
  132. self._peerConnections[peerId].remoteDescription.sdp && (self._peerConnections[peerId].remoteDescription.sdp.indexOf(
  133. 'm=application') === -1 || self._peerConnections[peerId].remoteDescription.sdp.indexOf('m=application 0') > 0)) {
  134. return;
  135. }
  136.  
  137. if (channelType === self.DATA_CHANNEL_TYPE.MESSAGING) {
  138. setTimeout(function () {
  139. if (self._peerConnections[peerId] &&
  140. self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.CLOSED &&
  141. (self._peerConnections[peerId].localDescription &&
  142. self._peerConnections[peerId].localDescription.type === self.HANDSHAKE_PROGRESS.OFFER)) {
  143. log.debug([peerId, 'RTCDataChannel', channelProp, 'Reviving Datachannel connection']);
  144. self._handleDatachannelStats('reconnecting', peerId, { label: channelName }, 'main');
  145. self._createDataChannel(peerId, channelName, bufferThreshold, true);
  146. }
  147. }, 100);
  148. }
  149. };
  150.  
  151. // Fixes for Firefox bug (49 is working) -> https://bugzilla.mozilla.org/show_bug.cgi?id=1118398
  152. if (AdapterJS.webrtcDetectedBrowser === 'firefox') {
  153. var hasTriggeredClose = false;
  154. var timeBlockAfterClosing = 0;
  155.  
  156. dataChannel.onclose = function () {
  157. if (!hasTriggeredClose) {
  158. hasTriggeredClose = true;
  159. onCloseHandlerFn();
  160. }
  161. };
  162.  
  163. var onFFClosed = setInterval(function () {
  164. if (dataChannel.readyState === self.DATA_CHANNEL_STATE.CLOSED ||
  165. hasTriggeredClose || timeBlockAfterClosing === 5) {
  166. clearInterval(onFFClosed);
  167.  
  168. if (!hasTriggeredClose) {
  169. hasTriggeredClose = true;
  170. onCloseHandlerFn();
  171. }
  172. // After 5 seconds from CLOSING state and Firefox is not rendering to close, we have to assume to close it.
  173. // It is dead! This fixes the case where if it's Firefox who closes the Datachannel, the connection will
  174. // still assume as CLOSING..
  175. } else if (dataChannel.readyState === self.DATA_CHANNEL_STATE.CLOSING) {
  176. timeBlockAfterClosing++;
  177. }
  178. }, 1000);
  179.  
  180. } else {
  181. dataChannel.onclose = onCloseHandlerFn;
  182. }
  183.  
  184. if (channelType === self.DATA_CHANNEL_TYPE.MESSAGING) {
  185. self._dataChannels[peerId].main = {
  186. channelName: channelName,
  187. channelType: channelType,
  188. transferId: null,
  189. streamId: null,
  190. channel: dataChannel
  191. };
  192. } else {
  193. self._dataChannels[peerId][channelName] = {
  194. channelName: channelName,
  195. channelType: channelType,
  196. transferId: null,
  197. streamId: null,
  198. channel: dataChannel
  199. };
  200. }
  201. };
  202.  
  203. /**
  204. * Function that refreshes the main messaging Datachannel.
  205. * @method refreshDatachannel
  206. * @param {String} [peerId] The target Peer ID to retrieve connection stats from.
  207. * @example
  208. * // Example 1: Retrieve offerer and refresh datachannel:
  209. * skylink.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  210. * if (channelType === skylink.DATA_CHANNEL_TYPE.MESSAGING &&
  211. * state === skylink.DATA_CHANNEL_STATE.CLOSED) {
  212. * var userWeight = skylink.getPeerInfo().config.priorityWeight;
  213. * var peerWeight = skylink.getPeerInfo(peerId).config.priorityWeight;
  214. * // Determine who is offerer because as per SM protocol, higher weight is offerer
  215. * if (userWeight > peerWeight) {
  216. * skylink.refreshDatachannel(peerId);
  217. * }
  218. * }
  219. * });
  220. * @for Skylink
  221. * @since 0.6.30
  222. */
  223.  
  224. Skylink.prototype.refreshDatachannel = function (peerId) {
  225.  
  226. var self = this;
  227. if(self._dataChannels[peerId] && self._dataChannels[peerId]["main"] && self._dataChannels[peerId].main.channel) {
  228. var channelName = self._dataChannels[peerId].main.channelName;
  229. var channelType = self._dataChannels[peerId].main.channelType;
  230. var channelProp = 'main';
  231. var bufferThreshold= self._dataChannels[peerId].main.channel.bufferedAmountLowThreshold || 0;
  232.  
  233. if (channelType === self.DATA_CHANNEL_TYPE.MESSAGING) {
  234. setTimeout(function () {
  235. if (self._peerConnections[peerId] &&
  236. self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.CLOSED &&
  237. (self._peerConnections[peerId].localDescription &&
  238. self._peerConnections[peerId].localDescription.type === self.HANDSHAKE_PROGRESS.OFFER)) {
  239. self._closeDataChannel(peerId, 'main', true);
  240. log.debug([peerId, 'RTCDataChannel', channelProp, 'Reviving Datachannel connection']);
  241. self._createDataChannel(peerId, channelName, bufferThreshold, true);
  242. }
  243. }, 100);
  244. }
  245. }
  246. else {
  247. log.debug([peerId, 'RTCDataChannel', 'Not a valid Datachannel connection']);
  248. }
  249. };
  250.  
  251. /**
  252. * Function that returns the Datachannel buffer threshold and amount.
  253. * @method _getDataChannelBuffer
  254. * @return {JSON} The buffered amount information.
  255. * @private
  256. * @for Skylink
  257. * @since 0.6.18
  258. */
  259. Skylink.prototype._getDataChannelBuffer = function (peerId, channelProp) {
  260. if (typeof peerId === 'object') {
  261. return {
  262. bufferedAmountLow: typeof peerId.bufferedAmountLow === 'number' ?
  263. peerId.bufferedAmountLow : parseInt(peerId.bufferedAmountLow, 10) || 0,
  264. bufferedAmountLowThreshold: typeof peerId.bufferedAmountLowThreshold === 'number' ?
  265. peerId.bufferedAmountLowThreshold : parseInt(peerId.bufferedAmountLowThreshold, 10) || 0
  266. };
  267. } else if (!(this._dataChannels[peerId] && this._dataChannels[peerId][channelProp] &&
  268. this._dataChannels[peerId][channelProp].channel)) {
  269. return {
  270. bufferedAmountLow: 0,
  271. bufferedAmountLowThreshold: 0
  272. };
  273. }
  274.  
  275. var channel = this._dataChannels[peerId][channelProp].channel;
  276.  
  277. return {
  278. bufferedAmountLow: typeof channel.bufferedAmountLow === 'number' ?
  279. channel.bufferedAmountLow : parseInt(channel.bufferedAmountLow, 10) || 0,
  280. bufferedAmountLowThreshold: typeof channel.bufferedAmountLowThreshold === 'number' ?
  281. channel.bufferedAmountLowThreshold : parseInt(channel.bufferedAmountLowThreshold, 10) || 0
  282. };
  283. };
  284.  
  285. /**
  286. * Function that sends data over the Datachannel connection.
  287. * @method _sendMessageToDataChannel
  288. * @private
  289. * @for Skylink
  290. * @since 0.5.2
  291. */
  292. Skylink.prototype._sendMessageToDataChannel = function(peerId, data, channelProp, doNotConvert) {
  293. var self = this;
  294.  
  295. // Set it as "main" (MESSAGING) Datachannel
  296. if (!channelProp || channelProp === peerId) {
  297. channelProp = 'main';
  298. }
  299.  
  300. // TODO: What happens when we want to send binary data over or ArrayBuffers?
  301. if (!(typeof data === 'object' && data) && !(data && typeof data === 'string')) {
  302. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping invalid data ->'], data);
  303. return;
  304. }
  305.  
  306. if (!(self._peerConnections[peerId] &&
  307. self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.CLOSED)) {
  308. log.warn([peerId, 'RTCDataChannel', channelProp,
  309. 'Dropping for sending message as Peer connection does not exists or is closed ->'], data);
  310. return;
  311. }
  312.  
  313. if (!(self._dataChannels[peerId] && self._dataChannels[peerId][channelProp])) {
  314. log.warn([peerId, 'RTCDataChannel', channelProp,
  315. 'Dropping for sending message as Datachannel connection does not exists ->'], data);
  316. return;
  317. }
  318.  
  319. var channelName = self._dataChannels[peerId][channelProp].channelName;
  320. var channelType = self._dataChannels[peerId][channelProp].channelType;
  321. var readyState = self._dataChannels[peerId][channelProp].channel.readyState;
  322. var messageType = typeof data === 'object' && data.type === self._DC_PROTOCOL_TYPE.MESSAGE ?
  323. self.DATA_CHANNEL_MESSAGE_ERROR.MESSAGE : self.DATA_CHANNEL_MESSAGE_ERROR.TRANSFER;
  324.  
  325. if (readyState !== self.DATA_CHANNEL_STATE.OPEN) {
  326. var notOpenError = 'Failed sending message as Datachannel connection state is not opened. Current ' +
  327. 'readyState is "' + readyState + '"';
  328.  
  329. log.error([peerId, 'RTCDataChannel', channelProp, notOpenError + ' ->'], data);
  330.  
  331. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.SEND_MESSAGE_ERROR, peerId, new Error(notOpenError),
  332. channelName, channelType, messageType, self._getDataChannelBuffer(peerId, channelProp));
  333.  
  334. throw new Error(notOpenError);
  335. }
  336.  
  337. try {
  338. if (!doNotConvert && typeof data === 'object') {
  339. log.debug([peerId, 'RTCDataChannel', channelProp, 'Sending "' + data.type + '" protocol message ->'], data);
  340.  
  341. self._dataChannels[peerId][channelProp].channel.send(JSON.stringify(data));
  342.  
  343. } else {
  344. log.debug([peerId, 'RTCDataChannel', channelProp, 'Sending data with size ->'],
  345. data.size || data.length || data.byteLength);
  346.  
  347. self._dataChannels[peerId][channelProp].channel.send(data);
  348. }
  349. } catch (error) {
  350. log.error([peerId, 'RTCDataChannel', channelProp, 'Failed sending ' + (!doNotConvert && typeof data === 'object' ?
  351. '"' + data.type + '" protocol message' : 'data') + ' ->'], error);
  352.  
  353. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.SEND_MESSAGE_ERROR, peerId,
  354. error, channelName, channelType, messageType, self._getDataChannelBuffer(peerId, channelProp));
  355.  
  356. throw error;
  357. }
  358. };
  359.  
  360. /**
  361. * Function that stops the Datachannel connection and removes object references.
  362. * @method _closeDataChannel
  363. * @private
  364. * @for Skylink
  365. * @since 0.1.0
  366. */
  367. Skylink.prototype._closeDataChannel = function(peerId, channelProp, isCloseMainChannel) {
  368. var self = this;
  369.  
  370. if (!self._dataChannels[peerId]) {
  371. log.warn([peerId, 'RTCDataChannel', channelProp || null,
  372. 'Aborting closing Datachannels as Peer connection does not have Datachannel sessions']);
  373. return;
  374. }
  375.  
  376. var closeFn = function (rChannelProp) {
  377. var channelName = self._dataChannels[peerId][rChannelProp].channelName;
  378. var channelType = self._dataChannels[peerId][rChannelProp].channelType;
  379.  
  380. if (self._dataChannels[peerId][rChannelProp].readyState !== self.DATA_CHANNEL_STATE.CLOSED) {
  381. log.debug([peerId, 'RTCDataChannel', channelProp, 'Closing Datachannel']);
  382.  
  383. self._handleDatachannelStats('closing', peerId, self._dataChannels[peerId][rChannelProp].channel, rChannelProp);
  384. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.CLOSING, peerId, null, channelName, channelType,
  385. null, self._getDataChannelBuffer(peerId, rChannelProp));
  386.  
  387. self._dataChannels[peerId][rChannelProp].channel.close();
  388.  
  389. delete self._dataChannels[peerId][rChannelProp];
  390. }
  391. };
  392.  
  393. if(isCloseMainChannel)
  394. {
  395. closeFn(channelProp);
  396. }
  397. else if (!channelProp || channelProp === 'main') {
  398. for (var channelNameProp in self._dataChannels[peerId]) {
  399. if (self._dataChannels[peerId].hasOwnProperty(channelNameProp)) {
  400. if (self._dataChannels[peerId][channelNameProp]) {
  401. closeFn(channelNameProp);
  402. }
  403. }
  404. }
  405.  
  406. delete self._dataChannels[peerId];
  407.  
  408. } else {
  409. if (!self._dataChannels[peerId][channelProp]) {
  410. log.warn([peerId, 'RTCDataChannel', channelProp, 'Aborting closing Datachannel as it does not exists']);
  411. return;
  412. }
  413.  
  414. closeFn(channelProp);
  415. }
  416. };