Temasys Documentation - SkylinkJS 0.9.2 - Web SDK

File: source/data-transfer.js

  1. /**
  2. * Function that starts an uploading data transfer from User to Peers.
  3. * @method sendBlobData
  4. * @param {Blob} data The Blob object.
  5. * @param {Number} [timeout=60] The timeout to wait for response from Peer.
  6. * @param {String|Array} [targetPeerId] The target Peer ID to start data transfer with.
  7. * - When provided as an Array, it will start uploading data transfers with all connections
  8. * with all the Peer IDs provided.
  9. * - When not provided, it will start uploading data transfers with all the currently connected Peers in the Room.
  10. * @param {Boolean} [sendChunksAsBinary=false] <blockquote class="info">
  11. * Note that this is currently not supported for MCU enabled Peer connections or Peer connections connecting from
  12. * Android, iOS and Linux SDKs. This would fallback to <code>transferInfo.chunkType</code> to
  13. * <code>BINARY_STRING</code> when MCU is connected. </blockquote> The flag if data transfer
  14. * binary data chunks should not be encoded as Base64 string during data transfers.
  15. * @param {Function} [callback] The callback function fired when request has completed.
  16. * <small>Function parameters signature is <code>function (error, success)</code></small>
  17. * <small>Function request completion is determined by the <a href="#event_dataTransferState">
  18. * <code>dataTransferState</code> event</a> triggering <code>state</code> parameter payload
  19. * as <code>UPLOAD_COMPLETED</code> for all Peers targeted for request success.</small>
  20. * @param {JSON} callback.error The error result in request.
  21. * <small>Defined as <code>null</code> when there are no errors in request</small>
  22. * @param {String} callback.error.transferId The data transfer ID.
  23. * <small>Defined as <code>null</code> when <code>sendBlobData()</code> fails to start data transfer.</small>
  24. * @param {Array} callback.error.listOfPeers The list Peer IDs targeted for the data transfer.
  25. * @param {JSON} callback.error.transferErrors The list of data transfer errors.
  26. * @param {Error|String} callback.error.transferErrors.#peerId The data transfer error associated
  27. * with the Peer ID defined in <code>#peerId</code> property.
  28. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  29. * are no Peer connections to start data transfer with.</small>
  30. * @param {JSON} callback.error.transferInfo The data transfer information.
  31. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  32. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
  33. * <code>percentage</code> and <code>data</code> property.</small>
  34. * @param {JSON} callback.success The success result in request.
  35. * <small>Defined as <code>null</code> when there are errors in request</small>
  36. * @param {String} callback.success.transferId The data transfer ID.
  37. * @param {Array} callback.success.listOfPeers The list Peer IDs targeted for the data transfer.
  38. * @param {JSON} callback.success.transferInfo The data transfer information.
  39. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  40. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
  41. * <code>percentage</code> property and <code>data</code>.</small>
  42. * @trigger <ol class="desc-seq">
  43. * <li>Checks if User is in Room. <ol>
  44. * <li>If User is not in Room: <ol><li><a href="#event_dataTransferState">
  45. * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
  46. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  47. * <li>Checks if there is any available Datachannel connections. <ol>
  48. * <li>If User is not in Room: <ol><li><a href="#event_dataTransferState">
  49. * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
  50. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  51. * <li>Checks if provided <code>data</code> parameter is valid. <ol>
  52. * <li>If it is invalid: <ol><li><a href="#event_dataTransferState">
  53. * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
  54. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  55. * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
  56. * <li>If Peer connection or session does not exists: <ol><li><a href="#event_dataTransferState">
  57. * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
  58. * as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  59. * <li>If Peer connection is not stable: <small>The stable state can be checked with <a href="#event_peerConnectionState">
  60. * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>STABLE</code>
  61. * for Peer.</small> <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
  62. * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  63. * <li>If Peer connection messaging Datachannel has not been opened: <small>This can be checked with
  64. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  65. * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
  66. * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataTransferState">
  67. * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
  68. * <li><b>ABORT</b> step and return error.</li></ol></li>
  69. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>method</a> and connected: <ol>
  70. * <li>If MCU Peer connection is not stable: <small>The stable state can be checked with <a href="#event_peerConnectionState">
  71. * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>STABLE</code>
  72. * and <code>peerId</code> value as <code>"MCU"</code> for MCU Peer.</small>
  73. * <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
  74. * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  75. * <li>If MCU Peer connection messaging Datachannel has not been opened: <small>This can be checked with
  76. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  77. * payload <code>state</code> as <code>OPEN</code>, <code>peerId</code> value as <code>"MCU"</code>
  78. * and <code>channelType</code> as <code>MESSAGING</code> for MCU Peer.</small>
  79. * <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
  80. * parameter payload <code>state</code> as <code>ERROR</code>.</li>
  81. * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  82. * <li>Checks if should open a new data Datachannel.<ol>
  83. * <li>If Peer supports simultaneous data transfer, open new data Datachannel: <small>If MCU is connected,
  84. * this opens a new data Datachannel with MCU Peer with all the Peers IDs information that supports
  85. * simultaneous data transfers targeted for the data transfer session instead of opening new data Datachannel
  86. * with all Peers targeted for the data transfer session.</small> <ol>
  87. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter
  88. * payload <code>state</code> as <code>CONNECTING</code> and <code>channelType</code> as <code>DATA</code>.
  89. * <small>Note that there is no timeout to wait for parameter payload <code>state</code> to be
  90. * <code>OPEN</code>.</small></li>
  91. * <li>If Datachannel has been created and opened successfully: <ol>
  92. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter payload
  93. * <code>state</code> as <code>OPEN</code> and <code>channelType</code> as <code>DATA</code>.</li></ol></li>
  94. * <li>Else: <ol><li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  95. * triggers parameter payload <code>state</code> as <code>CREATE_ERROR</code> and <code>channelType</code> as
  96. * <code>DATA</code>.</li><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
  97. * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
  98. * return error.</li></ol></li></ol></li><li>Else: <small>If MCU is connected,
  99. * this uses the messaging Datachannel with MCU Peer with all the Peers IDs information that supports
  100. * simultaneous data transfers targeted for the data transfer session instead of using the messaging Datachannels
  101. * with all Peers targeted for the data transfer session.</small> <ol><li>If messaging Datachannel connection has a
  102. * data transfer in-progress: <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  103. * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
  104. * return error.</li></ol></li><li>If there is any conflicting <a href="#method_streamData"><code>streamData()</code>
  105. * method</a> data streaming session: <small>If <code>sendChunksAsBinary</code> is provided as <code>true</code>,
  106. * it cannot start if existing data streaming session is expected binary data chunks, and if provided as
  107. * <code>false</code>, or method invoked is <a href="#method_sendURLData"><code>sendURLData()</code> method</a>,
  108. * or Peer is using string data chunks fallback due to its support despite provided as <code>true</code>,
  109. * it cannot start if existing data streaming session is expected string data chunks.</small><ol>
  110. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  111. * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
  112. * return error.</li></ol></li></li></ol></ol></li></ol></li>
  113. * <li>Starts the data transfer to Peer. <ol>
  114. * <li><a href="#event_incomingDataRequest"><code>incomingDataRequest</code> event</a> triggers.</li>
  115. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  116. * triggers parameter payload <code>state</code> as <code>USER_UPLOAD_REQUEST</code>.</li>
  117. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  118. * triggers parameter payload <code>state</code> as <code>UPLOAD_REQUEST</code>.</li>
  119. * <li>Peer invokes <a href="#method_acceptDataTransfer"><code>acceptDataTransfer()</code> method</a>. <ol>
  120. * <li>If parameter <code>accept</code> value is <code>true</code>: <ol>
  121. * <li>User starts upload data transfer to Peer. <ol>
  122. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  123. * triggers parameter payload <code>state</code> as <code>UPLOAD_STARTED</code>.</li>
  124. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  125. * triggers parameter payload <code>state</code> as <code>DOWNLOAD_STARTED</code>.</li></ol></li>
  126. * <li>If Peer / User invokes <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>: <ol>
  127. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  128. * <code>state</code> as <code>CANCEL</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  129. * <li>If data transfer has timeout errors: <ol>
  130. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  131. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  132. * <li>Checks for Peer connection and Datachannel connection during data transfer: <ol>
  133. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>
  134. * method</a> and connected: <ol>
  135. * <li>If MCU Datachannel has closed abruptly during data transfer: <ol>
  136. * <small>This can be checked with <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  137. * triggering parameter payload <code>state</code> as <code>CLOSED</code>, <code>peerId</code> value as
  138. * <code>"MCU"</code> and <code>channelType</code> as <code>DATA</code> for targeted Peers that supports simultaneous
  139. * data transfer or <code>MESSAGING</code> for targeted Peers that do not support it.</small> <ol>
  140. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  141. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  142. * <li>If MCU Peer connection has changed from not being stable: <ol>
  143. * <small>This can be checked with <a href="#event_peerConnectionState"><code>peerConnection</code> event</a>
  144. * triggering parameter payload <code>state</code> as not <code>STABLE</code>, <code>peerId</code> value as
  145. * <code>"MCU"</code>.</small> <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  146. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  147. * <li>If Peer connection has changed from not being stable: <ol>
  148. * <small>This can be checked with <a href="#event_peerConnectionState"><code>peerConnection</code> event</a>
  149. * triggering parameter payload <code>state</code> as not <code>STABLE</code>.</small> <ol>
  150. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  151. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
  152. * <li>Else: <ol><li>If Datachannel has closed abruptly during data transfer:
  153. * <small>This can be checked with <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  154. * triggering parameter payload <code>state</code> as <code>CLOSED</code> and <code>channelType</code>
  155. * as <code>DATA</code> for Peer that supports simultaneous data transfer or <code>MESSAGING</code>
  156. * for Peer that do not support it.</small> <ol>
  157. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  158. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
  159. * <li>If data transfer is still progressing: <ol>
  160. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  161. * triggers parameter payload <code>state</code> as <code>UPLOADING</code>.</li>
  162. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  163. * triggers parameter payload <code>state</code> as <code>DOWNLOADING</code>.</li></ol></li>
  164. * <li>If data transfer has completed <ol>
  165. * <li><a href="#event_incomingData"><code>incomingData</code> event</a> triggers.</li>
  166. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  167. * triggers parameter payload <code>state</code> as <code>UPLOAD_COMPLETED</code>.</li>
  168. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  169. * triggers parameter payload <code>state</code> as <code>DOWNLOAD_COMPLETED</code>.</li></ol></li></ol></li>
  170. * <li>If parameter <code>accept</code> value is <code>false</code>: <ol>
  171. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  172. * triggers parameter payload <code>state</code> as <code>REJECTED</code>.</li>
  173. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  174. * triggers parameter payload <code>state</code> as <code>USER_REJECTED</code>.</li>
  175. * <li><b>ABORT</b> step and return error.</li></ol></li></ol>
  176. * @example
  177. * &lt;body&gt;
  178. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(0)"&gt; 1s timeout (Default)
  179. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(120)"&gt; 2s timeout
  180. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(300)"&gt; 5s timeout
  181. * &lt;hr&gt;
  182. * &lt;input type="file" onchange="uploadFile(this.files[0], this.getAttribute('data'))" data="peerId"&gt;
  183. * &lt;input type="file" onchange="uploadFileGroup(this.files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB"&gt;
  184. * &lt;input type="file" onchange="uploadFileAll(this.files[0])" data=""&gt;
  185. * &lt;script&gt;
  186. * var transferTimeout = 0;
  187. *
  188. * function setTransferTimeout (timeout) {
  189. * transferTimeout = timeout;
  190. * }
  191. *
  192. * // Example 1: Upload data to a Peer
  193. * function uploadFile (file, peerId) {
  194. * var cb = function (error, success) {
  195. * if (error) return;
  196. * console.info("File has been transferred to '" + peerId + "' successfully");
  197. * };
  198. * if (transferTimeout > 0) {
  199. * skylinkDemo.sendBlobData(file, peerId, transferTimeout, cb);
  200. * } else {
  201. * skylinkDemo.sendBlobData(file, peerId, cb);
  202. * }
  203. * }
  204. *
  205. * // Example 2: Upload data to a list of Peers
  206. * function uploadFileGroup (file, peerIds) {
  207. * var cb = function (error, success) {
  208. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  209. * var listOfPeersErrors = error ? error.transferErrors : {};
  210. * for (var i = 0; i < listOfPeers.length; i++) {
  211. * if (listOfPeersErrors[listOfPeers[i]]) {
  212. * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
  213. * } else {
  214. * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
  215. * }
  216. * }
  217. * };
  218. * if (transferTimeout > 0) {
  219. * skylinkDemo.sendBlobData(file, peerIds, transferTimeout, cb);
  220. * } else {
  221. * skylinkDemo.sendBlobData(file, peerIds, cb);
  222. * }
  223. * }
  224. *
  225. * // Example 2: Upload data to a list of Peers
  226. * function uploadFileAll (file) {
  227. * var cb = function (error, success) {
  228. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  229. * var listOfPeersErrors = error ? error.transferErrors : {};
  230. * for (var i = 0; i < listOfPeers.length; i++) {
  231. * if (listOfPeersErrors[listOfPeers[i]]) {
  232. * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
  233. * } else {
  234. * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
  235. * }
  236. * }
  237. * };
  238. * if (transferTimeout > 0) {
  239. * skylinkDemo.sendBlobData(file, transferTimeout, cb);
  240. * } else {
  241. * skylinkDemo.sendBlobData(file, cb);
  242. * }
  243. * }
  244. * &lt;/script&gt;
  245. * &lt;/body&gt;
  246. * @for Skylink
  247. * @since 0.5.5
  248. */
  249. Skylink.prototype.sendBlobData = function(data, timeout, targetPeerId, sendChunksAsBinary, callback) {
  250. this._startDataTransfer(data, timeout, targetPeerId, sendChunksAsBinary, callback, 'blob');
  251. };
  252.  
  253. /**
  254. * Function that starts an uploading string data transfer from User to Peers.
  255. * @method sendURLData
  256. * @param {String} data The data string to transfer to Peer.
  257. * @param {Number} [timeout=60] The timeout to wait for response from Peer.
  258. * @param {String|Array} [targetPeerId] The target Peer ID to start data transfer with.
  259. * - When provided as an Array, it will start uploading data transfers with all connections
  260. * with all the Peer IDs provided.
  261. * - When not provided, it will start uploading data transfers with all the currently connected Peers in the Room.
  262. * @param {Function} [callback] The callback function fired when request has completed.
  263. * <small>Function parameters signature is <code>function (error, success)</code></small>
  264. * <small>Function request completion is determined by the <a href="#event_dataTransferState">
  265. * <code>dataTransferState</code> event</a> triggering <code>state</code> parameter payload
  266. * as <code>UPLOAD_COMPLETED</code> for all Peers targeted for request success.</small>
  267. * @param {JSON} callback.error The error result in request.
  268. * <small>Defined as <code>null</code> when there are no errors in request</small>
  269. * @param {String} callback.error.transferId The data transfer ID.
  270. * <small>Defined as <code>null</code> when <code>sendURLData()</code> fails to start data transfer.</small>
  271. * @param {Array} callback.error.listOfPeers The list Peer IDs targeted for the data transfer.
  272. * @param {JSON} callback.error.transferErrors The list of data transfer errors.
  273. * @param {Error|String} callback.error.transferErrors.#peerId The data transfer error associated
  274. * with the Peer ID defined in <code>#peerId</code> property.
  275. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  276. * are no Peer connections to start data transfer with.</small>
  277. * @param {JSON} callback.error.transferInfo The data transfer information.
  278. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  279. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
  280. * <code>percentage</code> property and <code>data</code>.</small>
  281. * @param {JSON} callback.success The success result in request.
  282. * <small>Defined as <code>null</code> when there are errors in request</small>
  283. * @param {String} callback.success.transferId The data transfer ID.
  284. * @param {Array} callback.success.listOfPeers The list Peer IDs targeted for the data transfer.
  285. * @param {JSON} callback.success.transferInfo The data transfer information.
  286. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  287. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
  288. * <code>percentage</code> property and <code>data</code>.</small>
  289. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  290. * <code>sendBlobData()</code> method</a>.</small>
  291. * @example
  292. * &lt;body&gt;
  293. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(0)"&gt; 1s timeout (Default)
  294. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(120)"&gt; 2s timeout
  295. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(300)"&gt; 5s timeout
  296. * &lt;hr&gt;
  297. * &lt;input type="file" onchange="showImage(this.files[0], this.getAttribute('data'))" data="peerId"&gt;
  298. * &lt;input type="file" onchange="showImageGroup(this.files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB"&gt;
  299. * &lt;input type="file" onchange="showImageAll(this.files[0])" data=""&gt;
  300. * &lt;image id="target-1" src=""&gt;
  301. * &lt;image id="target-2" src=""&gt;
  302. * &lt;image id="target-3" src=""&gt;
  303. * &lt;script&gt;
  304. * var transferTimeout = 0;
  305. *
  306. * function setTransferTimeout (timeout) {
  307. * transferTimeout = timeout;
  308. * }
  309. *
  310. * function retrieveImageDataURL(file, cb) {
  311. * var fr = new FileReader();
  312. * fr.onload = function () {
  313. * cb(fr.result);
  314. * };
  315. * fr.readAsDataURL(files[0]);
  316. * }
  317. *
  318. * // Example 1: Send image data URL to a Peer
  319. * function showImage (file, peerId) {
  320. * var cb = function (error, success) {
  321. * if (error) return;
  322. * console.info("Image has been transferred to '" + peerId + "' successfully");
  323. * };
  324. * retrieveImageDataURL(file, function (str) {
  325. * if (transferTimeout > 0) {
  326. * skylinkDemo.sendURLData(str, peerId, transferTimeout, cb);
  327. * } else {
  328. * skylinkDemo.sendURLData(str, peerId, cb);
  329. * }
  330. * document.getElementById("target-1").src = str;
  331. * });
  332. * }
  333. *
  334. * // Example 2: Send image data URL to a list of Peers
  335. * function showImageGroup (file, peerIds) {
  336. * var cb = function (error, success) {
  337. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  338. * var listOfPeersErrors = error ? error.transferErrors : {};
  339. * for (var i = 0; i < listOfPeers.length; i++) {
  340. * if (listOfPeersErrors[listOfPeers[i]]) {
  341. * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
  342. * } else {
  343. * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
  344. * }
  345. * }
  346. * };
  347. * retrieveImageDataURL(file, function (str) {
  348. * if (transferTimeout > 0) {
  349. * skylinkDemo.sendURLData(str, peerIds, transferTimeout, cb);
  350. * } else {
  351. * skylinkDemo.sendURLData(str, peerIds, cb);
  352. * }
  353. * document.getElementById("target-2").src = str;
  354. * });
  355. * }
  356. *
  357. * // Example 2: Send image data URL to a list of Peers
  358. * function uploadFileAll (file) {
  359. * var cb = function (error, success) {
  360. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  361. * var listOfPeersErrors = error ? error.transferErrors : {};
  362. * for (var i = 0; i < listOfPeers.length; i++) {
  363. * if (listOfPeersErrors[listOfPeers[i]]) {
  364. * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
  365. * } else {
  366. * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
  367. * }
  368. * }
  369. * };
  370. * retrieveImageDataURL(file, function (str) {
  371. * if (transferTimeout > 0) {
  372. * skylinkDemo.sendURLData(str, transferTimeout, cb);
  373. * } else {
  374. * skylinkDemo.sendURLData(str, cb);
  375. * }
  376. * document.getElementById("target-3").src = str;
  377. * });
  378. * }
  379. * &lt;/script&gt;
  380. * &lt;/body&gt;
  381. * @for Skylink
  382. * @since 0.6.1
  383. */
  384. Skylink.prototype.sendURLData = function(data, timeout, targetPeerId, callback) {
  385. this._startDataTransfer(data, timeout, targetPeerId, callback, null, 'data');
  386. };
  387.  
  388. /**
  389. * Function that accepts or rejects an upload data transfer request from Peer to User.
  390. * @method acceptDataTransfer
  391. * @param {String} peerId The Peer ID.
  392. * @param {String} transferId The data transfer ID.
  393. * @param {Boolean} [accept=false] The flag if User accepts the upload data transfer request from Peer.
  394. * @example
  395. * // Example 1: Accept Peer upload data transfer request
  396. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  397. * if (!isSelf) {
  398. * skylinkDemo.acceptDataTransfer(peerId, transferId, true);
  399. * }
  400. * });
  401. *
  402. * // Example 2: Reject Peer upload data transfer request
  403. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  404. * if (!isSelf) {
  405. * skylinkDemo.acceptDataTransfer(peerId, transferId, false);
  406. * }
  407. * });
  408. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  409. * <code>sendBlobData()</code> method</a> after <code>acceptDataTransfer()</code> method is invoked.</small>
  410. * @for Skylink
  411. * @since 0.6.1
  412. */
  413. Skylink.prototype.respondBlobRequest =
  414. Skylink.prototype.acceptDataTransfer = function (peerId, transferId, accept) {
  415. var self = this;
  416.  
  417. if (typeof transferId !== 'string' && typeof peerId !== 'string') {
  418. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
  419. 'data transfer ID or peer ID is not provided']);
  420. return;
  421. }
  422.  
  423. if (!self._dataChannels[peerId]) {
  424. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
  425. 'Peer does not have any Datachannel connections']);
  426. return;
  427. }
  428.  
  429. if (!self._dataTransfers[transferId]) {
  430. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
  431. 'invalid transfer ID is provided']);
  432. return;
  433. }
  434.  
  435. // Check Datachannel property in _dataChannels[peerId] list
  436. var channelProp = 'main';
  437. var dataChannelStateCbFn = null;
  438.  
  439. if (self._dataChannels[peerId][transferId]) {
  440. channelProp = transferId;
  441. }
  442.  
  443. // From here we start detecting as completion for data transfer downloads
  444. self.once('dataTransferState', function () {
  445. if (dataChannelStateCbFn) {
  446. self.off('dataChannelState', dataChannelStateCbFn);
  447. }
  448.  
  449. delete self._dataTransfers[transferId];
  450.  
  451. if (self._dataChannels[peerId]) {
  452. if (channelProp === 'main' && self._dataChannels[peerId].main) {
  453. self._dataChannels[peerId].main.transferId = null;
  454. }
  455.  
  456. if (channelProp === transferId) {
  457. self._closeDataChannel(peerId, transferId);
  458. }
  459. }
  460. }, function (state, evtTransferId, evtPeerId) {
  461. return evtTransferId === transferId && evtPeerId === peerId && [
  462. self.DATA_TRANSFER_STATE.ERROR,
  463. self.DATA_TRANSFER_STATE.CANCEL,
  464. self.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED,
  465. self.DATA_TRANSFER_STATE.USER_REJECTED].indexOf(state) > -1;
  466. });
  467.  
  468. if (accept) {
  469. log.debug([peerId, 'RTCDataChannel', transferId, 'Accepted data transfer and starting ...']);
  470.  
  471. dataChannelStateCbFn = function (state, evtPeerId, error, cN, cT) {
  472. log.error([peerId, 'RTCDataChannel', channelProp, 'Data transfer "' + transferId + '" has been terminated due to connection.']);
  473.  
  474. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, peerId,
  475. self._getTransferInfo(transferId, peerId, true, false, false), {
  476. transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
  477. message: new Error('Data transfer terminated as Peer Datachannel connection closed abruptly.')
  478. });
  479. };
  480.  
  481. self.once('dataChannelState', dataChannelStateCbFn, function (state, evtPeerId, error, channelName, channelType) {
  482. if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  483. self.off('dataChannelState', dataChannelStateCbFn);
  484. return;
  485. }
  486.  
  487. return evtPeerId === peerId && (channelProp === 'main' ?
  488. channelType === self.DATA_CHANNEL_STATE.MESSAGING : channelName === transferId) && [
  489. self.DATA_CHANNEL_STATE.CLOSING,
  490. self.DATA_CHANNEL_STATE.CLOSED,
  491. self.DATA_CHANNEL_STATE.ERROR].indexOf(state) > -1;
  492. });
  493.  
  494. // Send ACK protocol to start data transfer
  495. // MCU sends the data transfer from the "P2P" Datachannel
  496. self._sendMessageToDataChannel(peerId, {
  497. type: self._DC_PROTOCOL_TYPE.ACK,
  498. sender: self._user.sid,
  499. ackN: 0
  500. }, channelProp);
  501.  
  502. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOAD_STARTED, transferId, peerId,
  503. self._getTransferInfo(transferId, peerId, true, false, false), null);
  504.  
  505. } else {
  506. log.warn([peerId, 'RTCDataChannel', transferId, 'Rejected data transfer and data transfer request has been aborted']);
  507.  
  508. // Send ACK protocol to terminate data transfer request
  509. // MCU sends the data transfer from the "P2P" Datachannel
  510. self._sendMessageToDataChannel(peerId, {
  511. type: self._DC_PROTOCOL_TYPE.ACK,
  512. sender: self._user.sid,
  513. ackN: -1
  514. }, channelProp);
  515.  
  516. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.USER_REJECTED, transferId, peerId,
  517. self._getTransferInfo(transferId, peerId, true, false, false), {
  518. message: new Error('Data transfer terminated as User has rejected data transfer request.'),
  519. transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD
  520. });
  521. }
  522. };
  523.  
  524. /**
  525. * <blockquote class="info">
  526. * For MCU enabled Peer connections, the cancel data transfer functionality may differ, as it
  527. * will result in all Peers related to the data transfer ID to be terminated.
  528. * </blockquote>
  529. * Function that terminates a currently uploading / downloading data transfer from / to Peer.
  530. * @method cancelDataTransfer
  531. * @param {String} peerId The Peer ID.
  532. * @param {String} transferId The data transfer ID.
  533. * @example
  534. * // Example 1: Cancel Peer data transfer
  535. * var transferSessions = {};
  536. *
  537. * skylinkDemo.on("dataTransferState", function (state, transferId, peerId) {
  538. * if ([skylinkDemo.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
  539. * skylinkDemo.DATA_TRANSFER_STATE.UPLOAD_STARTED].indexOf(state) > -1) {
  540. * if (!Array.isArray(transferSessions[transferId])) {
  541. * transferSessions[transferId] = [];
  542. * }
  543. * transferSessions[transferId].push(peerId);
  544. * } else {
  545. * transferSessions[transferId].splice(transferSessions[transferId].indexOf(peerId), 1);
  546. * }
  547. * });
  548. *
  549. * function cancelTransfer (peerId, transferId) {
  550. * skylinkDemo.cancelDataTransfer(peerId, transferId);
  551. * }
  552. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  553. * <code>sendBlobData()</code> method</a> after <code>cancelDataTransfer()</code> method is invoked.</small>
  554. * @for Skylink
  555. * @since 0.6.1
  556. */
  557. Skylink.prototype.cancelBlobTransfer =
  558. Skylink.prototype.cancelDataTransfer = function (peerId, transferId) {
  559. var self = this;
  560.  
  561. if (!(transferId && typeof transferId === 'string')) {
  562. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as data transfer ID is not provided']);
  563. return;
  564. }
  565.  
  566. if (!(peerId && typeof peerId === 'string')) {
  567. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as peer ID is not provided']);
  568. return;
  569. }
  570.  
  571. if (!self._dataTransfers[transferId]) {
  572. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
  573. 'data transfer session does not exists.']);
  574. return;
  575. }
  576.  
  577. log.debug([peerId, 'RTCDataChannel', transferId, 'Canceling data transfer ...']);
  578.  
  579. /**
  580. * Emit data state event function.
  581. */
  582. var emitEventFn = function (peers, transferInfoPeerId) {
  583. for (var i = 0; i < peers.length; i++) {
  584. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.CANCEL, transferId, peers[i],
  585. self._getTransferInfo(transferId, transferInfoPeerId, false, false, false), {
  586. transferType: self._dataTransfers[transferId].direction,
  587. message: new Error('User cancelled download transfer')
  588. });
  589. }
  590. };
  591.  
  592. // For uploading from Peer to MCU case of broadcast
  593. if (self._hasMCU && self._dataTransfers[transferId].direction === self.DATA_TRANSFER_TYPE.UPLOAD) {
  594. if (!self._dataChannels.MCU) {
  595. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
  596. 'Peer does not have any Datachannel connections']);
  597. return;
  598. }
  599.  
  600. // We abort all data transfers to all Peers if uploading via MCU since it broadcasts to MCU
  601. log.warn([peerId, 'RTCDataChannel', transferId, 'Aborting all data transfers to Peers']);
  602.  
  603. // If data transfer to MCU broadcast has interop Peers, send to MCU via the "main" Datachannel
  604. if (Object.keys(self._dataTransfers[transferId].peers.main).length > 0) {
  605. self._sendMessageToDataChannel('MCU', {
  606. type: self._DC_PROTOCOL_TYPE.CANCEL,
  607. sender: self._user.sid,
  608. content: 'Peer cancelled download transfer',
  609. name: self._dataTransfers[transferId].name,
  610. ackN: 0
  611. }, 'main');
  612. }
  613.  
  614. // If data transfer to MCU broadcast has non-interop Peers, send to MCU via the new Datachanel
  615. if (Object.keys(self._dataTransfers[transferId].peers[transferId]).length > 0) {
  616. self._sendMessageToDataChannel('MCU', {
  617. type: self._DC_PROTOCOL_TYPE.CANCEL,
  618. sender: self._user.sid,
  619. content: 'Peer cancelled download transfer',
  620. name: self._dataTransfers[transferId].name,
  621. ackN: 0
  622. }, transferId);
  623. }
  624.  
  625. emitEventFn(Object.keys(self._dataTransfers[transferId].peers.main).concat(
  626. Object.keys(self._dataTransfers[transferId].peers[transferId])));
  627. } else {
  628. var channelProp = 'main';
  629.  
  630. if (!self._dataChannels[peerId]) {
  631. log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
  632. 'Peer does not have any Datachannel connections']);
  633. return;
  634. }
  635.  
  636. if (self._dataChannels[peerId][transferId]) {
  637. channelProp = transferId;
  638. }
  639.  
  640. self._sendMessageToDataChannel(peerId, {
  641. type: self._DC_PROTOCOL_TYPE.CANCEL,
  642. sender: self._user.sid,
  643. content: 'Peer cancelled download transfer',
  644. name: self._dataTransfers[transferId].name,
  645. ackN: 0
  646. }, channelProp);
  647.  
  648. emitEventFn([peerId], peerId);
  649. }
  650. };
  651.  
  652. /**
  653. * Function that sends a message to Peers via the Datachannel connection.
  654. * <small>Consider using <a href="#method_sendURLData"><code>sendURLData()</code> method</a> if you are
  655. * sending large strings to Peers.</small>
  656. * @method sendP2PMessage
  657. * @param {String|JSON} message The message.
  658. * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
  659. * - When provided as an Array, it will send the message to only Peers which IDs are in the list.
  660. * - When not provided, it will broadcast the message to all connected Peers with Datachannel connection in the Room.
  661. * @trigger <ol class="desc-seq">
  662. * <li>Sends P2P message to all targeted Peers. <ol>
  663. * <li>If Peer connection Datachannel has not been opened: <small>This can be checked with
  664. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  665. * triggering parameter payload <code>state</code> as <code>OPEN</code> and
  666. * <code>channelType</code> as <code>MESSAGING</code> for Peer.</small> <ol>
  667. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers
  668. * parameter payload <code>state</code> as <code>SEND_MESSAGE_ERROR</code>.</li>
  669. * <li><b>ABORT</b> step and return error.</li></ol></li>
  670. * <li><a href="#event_incomingMessage"><code>incomingMessage</code> event</a> triggers
  671. * parameter payload <code>message.isDataChannel</code> value as <code>true</code> and
  672. * <code>isSelf</code> value as <code>true</code>.</li></ol></li></ol>
  673. * @example
  674. * // Example 1: Broadcasting to all Peers
  675. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  676. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  677. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  678. * skylinkDemo.sendP2PMessage("Hi all!");
  679. * }
  680. * });
  681. *
  682. * // Example 2: Sending to specific Peers
  683. * var peersInExclusiveParty = [];
  684. *
  685. * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
  686. * if (isSelf) return;
  687. * if (peerInfo.userData.exclusive) {
  688. * peersInExclusiveParty[peerId] = false;
  689. * }
  690. * });
  691. *
  692. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  693. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  694. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  695. * peersInExclusiveParty[peerId] = true;
  696. * }
  697. * });
  698. *
  699. * function updateExclusivePartyStatus (message) {
  700. * var readyToSend = [];
  701. * for (var p in peersInExclusiveParty) {
  702. * if (peersInExclusiveParty.hasOwnProperty(p)) {
  703. * readyToSend.push(p);
  704. * }
  705. * }
  706. * skylinkDemo.sendP2PMessage(message, readyToSend);
  707. * }
  708. * @for Skylink
  709. * @since 0.5.5
  710. */
  711. Skylink.prototype.sendP2PMessage = function(message, targetPeerId) {
  712. var listOfPeers = Object.keys(this._dataChannels);
  713. var isPrivate = false;
  714.  
  715. if (Array.isArray(targetPeerId)) {
  716. listOfPeers = targetPeerId;
  717. isPrivate = true;
  718. } else if (targetPeerId && typeof targetPeerId === 'string') {
  719. listOfPeers = [targetPeerId];
  720. isPrivate = true;
  721. }
  722.  
  723. if (!this._inRoom || !(this._user && this._user.sid)) {
  724. log.error('Unable to send message as User is not in Room. ->', message);
  725. return;
  726. }
  727.  
  728. if (!this._initOptions.enableDataChannel) {
  729. log.error('Unable to send message as User does not have Datachannel enabled. ->', message);
  730. return;
  731. }
  732.  
  733. // Loop out unwanted Peers
  734. for (var i = 0; i < listOfPeers.length; i++) {
  735. var peerId = listOfPeers[i];
  736.  
  737. if (!this._hasMCU && !this._dataChannels[peerId]) {
  738. log.error([peerId, 'RTCDataChannel', null, 'Dropping of sending message to Peer as ' +
  739. 'Datachannel connection does not exists']);
  740. listOfPeers.splice(i, 1);
  741. i--;
  742. } else if (peerId === 'MCU') {
  743. listOfPeers.splice(i, 1);
  744. i--;
  745. } else if (!this._hasMCU) {
  746. log.debug([peerId, 'RTCDataChannel', null, 'Sending ' + (isPrivate ? 'private' : '') +
  747. ' P2P message to Peer']);
  748.  
  749. this._sendMessageToDataChannel(peerId, {
  750. type: this._DC_PROTOCOL_TYPE.MESSAGE,
  751. isPrivate: isPrivate,
  752. sender: this._user.sid,
  753. target: peerId,
  754. data: message
  755. }, 'main');
  756. }
  757. }
  758.  
  759. if (listOfPeers.length === 0) {
  760. log.warn('Currently there are no Peers to send P2P message to (unless the message is queued ' +
  761. 'and there are Peer connected by then).');
  762. }
  763.  
  764. if (this._hasMCU) {
  765. log.debug(['MCU', 'RTCDataChannel', null, 'Broadcasting ' + (isPrivate ? 'private' : '') +
  766. ' P2P message to Peers']);
  767.  
  768. this._sendMessageToDataChannel('MCU', {
  769. type: this._DC_PROTOCOL_TYPE.MESSAGE,
  770. isPrivate: isPrivate,
  771. sender: this._user.sid,
  772. target: listOfPeers,
  773. data: message
  774. }, 'main');
  775. }
  776.  
  777. this._trigger('incomingMessage', {
  778. content: message,
  779. isPrivate: isPrivate,
  780. targetPeerId: targetPeerId || null,
  781. listOfPeers: listOfPeers,
  782. isDataChannel: true,
  783. senderPeerId: this._user.sid
  784. }, this._user.sid, this.getPeerInfo(), true);
  785. };
  786.  
  787. /**
  788. * <blockquote class="info">
  789. * Note that this feature is not supported by MCU enabled Peer connections and the
  790. * <code>enableSimultaneousTransfers</code> flag has to be enabled in the <a href="#method_init">
  791. * <code>init()</code> method</a> in order for this functionality to work.<br>
  792. * To start streaming data, see the <a href="#method_streamData"><code>streamData()</code>
  793. * method</a>. To stop data streaming session, see the <a href="#method_stopStreamingData"><code>
  794. * stopStreamingData()</code> method</a>.
  795. * </blockquote>
  796. * Function that starts a data chunks streaming session from User to Peers.
  797. * @method startStreamingData
  798. * @param {Boolean} [isStringStream=false] The flag if data streaming session sending data chunks
  799. * should be expected as string data chunks sent.
  800. * <small>By default, data chunks are expected to be sent in Blob or ArrayBuffer, and ArrayBuffer
  801. * data chunks will be converted to Blob.</small>
  802. * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
  803. * - When provided as an Array, it will start streaming session to only Peers which IDs are in the list.
  804. * - When not provided, it will start the streaming session to all connected Peers with Datachannel connection in the Room.
  805. * @trigger <ol class="desc-seq">
  806. * @trigger <ol class="desc-seq">
  807. * <li>Checks if User is in Room. <ol>
  808. * <li>If User is not in Room: <ol><li><a href="#event_dataStreamState">
  809. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
  810. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  811. * <li>Checks if there is any available Datachannel connections. <ol>
  812. * <li>If User is not in Room: <ol><li><a href="#event_dataStreamState">
  813. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
  814. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  815. * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
  816. * <li>If Peer connection or session does not exists: <ol><li><a href="#event_dataStreamState">
  817. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
  818. * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  819. * <li>If Peer connection messaging Datachannel has not been opened: <small>This can be checked with
  820. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  821. * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
  822. * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
  823. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li>
  824. * <li><b>ABORT</b> step and return error.</li></ol></li>
  825. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>method</a> and connected: <ol>
  826. * <li>If MCU Peer connection messaging Datachannel has not been opened: <small>This can be checked with
  827. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  828. * payload <code>state</code> as <code>OPEN</code>, <code>peerId</code> value as <code>"MCU"</code>
  829. * and <code>channelType</code> as <code>MESSAGING</code> for MCU Peer.</small>
  830. * <ol><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a> triggers
  831. * parameter payload <code>state</code> as <code>START_ERROR</code>.</li>
  832. * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  833. * <li>Checks if should open a new data Datachannel.<ol>
  834. * <li>If Peer supports simultaneous data streaming, open new data Datachannel: <small>If MCU is connected,
  835. * this opens a new data Datachannel with MCU Peer with all the Peers IDs information that supports
  836. * simultaneous data transfers targeted for the data streaming session instead of opening new data Datachannel
  837. * with all Peers targeted for the data streaming session.</small> <ol>
  838. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter
  839. * payload <code>state</code> as <code>CONNECTING</code> and <code>channelType</code> as <code>DATA</code>.
  840. * <small>Note that there is no timeout to wait for parameter payload <code>state</code> to be
  841. * <code>OPEN</code>.</small></li>
  842. * <li>If Datachannel has been created and opened successfully: <ol>
  843. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter payload
  844. * <code>state</code> as <code>OPEN</code> and <code>channelType</code> as <code>DATA</code>.</li></ol></li>
  845. * <li>Else: <ol><li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  846. * triggers parameter payload <code>state</code> as <code>CREATE_ERROR</code> and <code>channelType</code> as
  847. * <code>DATA</code>.</li><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a> triggers
  848. * parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
  849. * return error.</li></ol></li></ol></li><li>Else: <small>If MCU is connected,
  850. * this uses the messaging Datachannel with MCU Peer with all the Peers IDs information that supports
  851. * simultaneous data transfers targeted for the data streaming session instead of using the messaging Datachannels
  852. * with all Peers targeted for the data streaming session.</small> <ol><li>If messaging Datachannel connection has a
  853. * data streaming in-progress: <ol><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  854. * triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
  855. * return error.</li></ol></li><li>If there is any conflicting <a href="#method_streamData"><code>streamData()</code>
  856. * method</a> data streaming session: <small>If <code>isStringStream</code> is provided as <code>true</code> and
  857. * <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a> or <a href="#method_sendURLData">
  858. * <code>sendURLData()</code> method</a> has an existing binary string transfer, it cannot start string data
  859. * streaming session. Else if <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a>
  860. * has an existing binary data transfer, it cannot start binary data streaming session.</small><ol>
  861. * <li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  862. * triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
  863. * return error.</li></ol></li></li></ol></ol></li></ol></li>
  864. * <li>Starts the data streaming session with Peer. <ol>
  865. * <li><a href="#event_incomingDataStreamStarted"><code>incomingDataStreamStarted</code> event</a> triggers.</li>
  866. * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  867. * triggers parameter payload <code>state</code> as <code>SENDING_STARTED</code>.</li>
  868. * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  869. * triggers parameter payload <code>state</code> as <code>RECEIVING_STARTED</code>.</li></ol></li></ol>
  870. * @example
  871. * // Example 1: Start streaming to all Peers
  872. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  873. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  874. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  875. * skylinkDemo.startStreamingData(false);
  876. * }
  877. * });
  878. *
  879. * // Example 2: Start streaming to specific Peers
  880. * var peersInExclusiveParty = [];
  881. *
  882. * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
  883. * if (isSelf) return;
  884. * if (peerInfo.userData.exclusive) {
  885. * peersInExclusiveParty[peerId] = false;
  886. * }
  887. * });
  888. *
  889. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  890. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  891. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  892. * peersInExclusiveParty[peerId] = true;
  893. * }
  894. * });
  895. *
  896. * function updateExclusivePartyStatus (message) {
  897. * var readyToSend = [];
  898. * for (var p in peersInExclusiveParty) {
  899. * if (peersInExclusiveParty.hasOwnProperty(p)) {
  900. * readyToSend.push(p);
  901. * }
  902. * }
  903. * skylinkDemo.startStreamingData(message, readyToSend);
  904. * }
  905. * @beta
  906. * @for Skylink
  907. * @since 0.6.18
  908. */
  909. Skylink.prototype.startStreamingData = function(isStringStream, targetPeerId) {
  910. var self = this;
  911. var listOfPeers = Object.keys(self._dataChannels);
  912. var isPrivate = false;
  913. var sessionChunkType = 'binary';
  914.  
  915. if (Array.isArray(targetPeerId)) {
  916. listOfPeers = targetPeerId;
  917. isPrivate = true;
  918. } else if (targetPeerId && typeof targetPeerId === 'string') {
  919. listOfPeers = [targetPeerId];
  920. isPrivate = true;
  921. }
  922.  
  923. if (Array.isArray(isStringStream)) {
  924. listOfPeers = isStringStream;
  925. targetPeerId = isStringStream;
  926. isPrivate = true;
  927. } else if (isStringStream && typeof isStringStream === 'string') {
  928. listOfPeers = [isStringStream];
  929. targetPeerId = isStringStream;
  930. isPrivate = true;
  931. } else if (isStringStream && typeof isStringStream === 'boolean') {
  932. sessionChunkType = 'string';
  933. }
  934.  
  935. var sessionInfo = {
  936. chunk: null,
  937. chunkSize: 0,
  938. chunkType: sessionChunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
  939. isPrivate: isPrivate,
  940. isStringStream: sessionChunkType === 'string',
  941. senderPeerId: self._user && self._user.sid ? self._user.sid : null
  942. };
  943.  
  944. // Remove MCU from list
  945. if (listOfPeers.indexOf('MCU') > -1) {
  946. listOfPeers.splice(listOfPeers.indexOf('MCU'), 1);
  947. }
  948.  
  949. var emitErrorBeforeStreamingFn = function (error) {
  950. log.error(error);
  951.  
  952. /*if (listOfPeers.length > 0) {
  953. for (var i = 0; i < listOfPeers.length; i++) {
  954. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, null,
  955. listOfPeers[i], sessionInfo, new Error(error));
  956. }
  957. } else {
  958. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, null,
  959. null, sessionInfo, new Error(error));
  960. }*/
  961. };
  962.  
  963. if (!this._inRoom || !(this._user && this._user.sid)) {
  964. return emitErrorBeforeStreamingFn('Unable to start data streaming as User is not in Room.');
  965. }
  966.  
  967. if (!this._initOptions.enableDataChannel) {
  968. return emitErrorBeforeStreamingFn('Unable to start data streaming as User does not have Datachannel enabled.');
  969. }
  970.  
  971. if (listOfPeers.length === 0) {
  972. return emitErrorBeforeStreamingFn('Unable to start data streaming as there are no Peers to start session with.');
  973. }
  974.  
  975. if (self._hasMCU) {
  976. return emitErrorBeforeStreamingFn('Unable to start data streaming as this feature is current not supported by MCU yet.');
  977. }
  978.  
  979. if (!self._initOptions.enableSimultaneousTransfers) {
  980. return emitErrorBeforeStreamingFn('Unable to start data streaming as this feature requires simultaneous data transfers to be enabled');
  981. }
  982.  
  983. var transferId = 'stream_' + (self._user && self._user.sid ? self._user.sid : '-') + '_' + (new Date()).getTime();
  984. var peersInterop = [];
  985. var peersNonInterop = [];
  986. var sessions = {};
  987. var listenToPeerFn = function (peerId, channelProp) {
  988. var hasStarted = false;
  989. sessions[peerId] = channelProp;
  990.  
  991. self.once('dataStreamState', function () {}, function (state, evtTransferId, evtPeerId, evtSessionInfo) {
  992. if (!(evtTransferId === transferId && evtPeerId === peerId)) {
  993. return;
  994. }
  995.  
  996. var dataChunk = evtSessionInfo.chunk;
  997. var updatedSessionInfo = clone(evtSessionInfo);
  998. delete updatedSessionInfo.chunk;
  999.  
  1000. if (state === self.DATA_STREAM_STATE.SENDING_STARTED) {
  1001. hasStarted = true;
  1002. return;
  1003. }
  1004.  
  1005. if (hasStarted && [self.DATA_STREAM_STATE.ERROR, self.DATA_STREAM_STATE.SENDING_STOPPED].indexOf(state) > -1) {
  1006. if (channelProp === transferId) {
  1007. self._closeDataChannel(peerId, transferId);
  1008. }
  1009.  
  1010. if (self._dataStreams[transferId] && self._dataStreams[transferId].sessions[peerId]) {
  1011. delete self._dataStreams[transferId].sessions[peerId];
  1012.  
  1013. if (Object.keys(self._dataStreams[transferId].sessions).length === 0) {
  1014. delete self._dataStreams[transferId];
  1015. }
  1016. }
  1017. return true;
  1018. }
  1019. });
  1020. };
  1021.  
  1022. // Loop out unwanted Peers
  1023. for (var i = 0; i < listOfPeers.length; i++) {
  1024. var peerId = listOfPeers[i];
  1025. var error = null;
  1026. var dtProtocolVersion = ((self._peerInformations[peerId] || {}).agent || {}).DTProtocolVersion || '';
  1027. var channelProp = self._isLowerThanVersion(dtProtocolVersion, '0.1.2') || !self._initOptions.enableSimultaneousTransfers ? 'main' : transferId;
  1028.  
  1029. if (!(self._dataChannels[peerId] && self._dataChannels[peerId].main)) {
  1030. error = 'Datachannel connection does not exists';
  1031. } else if (self._hasMCU && !(self._dataChannels.MCU && self._dataChannels.MCU.main)) {
  1032. error = 'MCU Datachannel connection does not exists';
  1033. } else if (self._isLowerThanVersion(dtProtocolVersion, '0.1.3')) {
  1034. error = 'Peer DTProtocolVersion does not support data streaming. (received: "' + dtProtocolVersion + '", expected: "0.1.3")';
  1035. } else {
  1036. if (channelProp === 'main') {
  1037. var dataTransferId = self._hasMCU ? self._dataChannels.MCU.main.transferId : self._dataChannels[peerId].main.transferId;
  1038.  
  1039. if (self._dataChannels[peerId].main.streamId) {
  1040. error = 'Peer Datachannel currently has an active data transfer session.';
  1041. } else if (self._hasMCU && self._dataChannels.MCU.main.streamId) {
  1042. error = 'MCU Peer Datachannel currently has an active data transfer session.';
  1043. } else if (self._dataTransfers[dataTransferId] && self._dataTransfers[dataTransferId].sessionChunkType === sessionChunkType) {
  1044. error = (self._hasMCU ? 'MCU ' : '') + 'Peer Datachannel currently has an active ' + sessionChunkType + ' data transfer.';
  1045. } else {
  1046. peersInterop.push(peerId);
  1047. }
  1048. } else {
  1049. peersNonInterop.push(peerId);
  1050. }
  1051. }
  1052.  
  1053. if (error) {
  1054. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, new Error(error));
  1055. listOfPeers.splice(i, 1);
  1056. i--;
  1057. } else {
  1058. listenToPeerFn(peerId, channelProp);
  1059. }
  1060. }
  1061.  
  1062. if (listOfPeers.length === 0) {
  1063. log.warn('There are no Peers to start data session with.');
  1064. return;
  1065. }
  1066.  
  1067. self._dataStreams[transferId] = {
  1068. sessions: sessions,
  1069. chunkType: sessionChunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
  1070. sessionChunkType: sessionChunkType,
  1071. isPrivate: isPrivate,
  1072. isStringStream: sessionChunkType === 'string',
  1073. senderPeerId: self._user && self._user.sid ? self._user.sid : null,
  1074. isUpload: true
  1075. };
  1076.  
  1077. var startDataSessionFn = function (peerId, channelProp, targetPeers) {
  1078. self.once('dataChannelState', function () {}, function (state, evtPeerId, channelName, channelType, error) {
  1079. if (!self._dataStreams[transferId]) {
  1080. return true;
  1081. }
  1082.  
  1083. if (!(evtPeerId === peerId && (channelProp === 'main' ? channelType === self.DATA_CHANNEL_TYPE.MESSAGING :
  1084. channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA))) {
  1085. return;
  1086. }
  1087.  
  1088. if ([self.DATA_CHANNEL_STATE.ERROR, self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1) {
  1089. var updatedError = new Error(error && error.message ? error.message :
  1090. 'Failed data transfer as datachannel state is "' + state + '".');
  1091.  
  1092. if (peerId === 'MCU') {
  1093. for (var mp = 0; mp < targetPeers.length; mp++) {
  1094. self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, targetPeers[mp], sessionInfo, updatedError);
  1095. }
  1096. } else {
  1097. self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo, updatedError);
  1098. }
  1099. return true;
  1100. }
  1101. });
  1102.  
  1103. if (!(self._dataChannels[peerId][channelProp] &&
  1104. self._dataChannels[peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN)) {
  1105. var notOpenError = new Error('Failed starting data streaming session as channel is not opened.');
  1106. if (peerId === 'MCU') {
  1107. for (i = 0; i < targetPeers.length; i++) {
  1108. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, targetPeers[i], sessionInfo, notOpenError);
  1109. }
  1110. } else {
  1111. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, notOpenError);
  1112. }
  1113. }
  1114.  
  1115. self._sendMessageToDataChannel(peerId, {
  1116. type: self._DC_PROTOCOL_TYPE.WRQ,
  1117. transferId: transferId,
  1118. name: transferId,
  1119. size: 0,
  1120. originalSize: 0,
  1121. dataType: 'fastBinaryStart',
  1122. mimeType: null,
  1123. chunkType: sessionChunkType,
  1124. chunkSize: 0,
  1125. timeout: 0,
  1126. isPrivate: isPrivate,
  1127. sender: self._user.sid,
  1128. agent: AdapterJS.webrtcDetectedBrowser,
  1129. version: AdapterJS.webrtcDetectedVersion,
  1130. target: peerId === 'MCU' ? targetPeers : peerId
  1131. }, channelProp);
  1132. self._dataChannels[peerId][channelProp].streamId = transferId;
  1133.  
  1134. var updatedSessionInfo = clone(sessionInfo);
  1135. delete updatedSessionInfo.chunk;
  1136.  
  1137. if (peerId === 'MCU') {
  1138. for (var tp = 0; tp < targetPeers.length; tp++) {
  1139. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STARTED, transferId, targetPeers[tp], sessionInfo, null);
  1140. self._trigger('incomingDataStreamStarted', transferId, targetPeers[tp], updatedSessionInfo, true);
  1141. }
  1142. } else {
  1143. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STARTED, transferId, peerId, sessionInfo, null);
  1144. self._trigger('incomingDataStreamStarted', transferId, peerId, updatedSessionInfo, true);
  1145. }
  1146. };
  1147.  
  1148. var waitForChannelOpenFn = function (peerId, targetPeers) {
  1149. self.once('dataChannelState', function (state, evtPeerId, error) {
  1150. if (state === self.DATA_CHANNEL_STATE.CREATE_ERROR) {
  1151. if (peerId === 'MCU') {
  1152. for (var mp = 0; mp < targetPeers.length; mp++) {
  1153. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, targetPeers[mp], sessionInfo, error);
  1154. }
  1155. } else {
  1156. self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, error);
  1157. }
  1158. return;
  1159. }
  1160. startDataSessionFn(peerId, transferId, targetPeers);
  1161. }, function (state, evtPeerId, error, channelName, channelType) {
  1162. if (evtPeerId === peerId && channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA) {
  1163. return [self.DATA_CHANNEL_STATE.CREATE_ERROR, self.DATA_CHANNEL_STATE.OPEN].indexOf(state) > -1;
  1164. }
  1165. });
  1166. self._createDataChannel(peerId, transferId, sessionChunkType === 'string' ? self._CHUNK_DATAURL_SIZE :
  1167. (AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE));
  1168. };
  1169.  
  1170. if (peersNonInterop.length > 0) {
  1171. if (self._hasMCU) {
  1172. waitForChannelOpenFn('MCU', peersNonInterop);
  1173. } else {
  1174. for (var pni = 0; pni < peersNonInterop.length; pni++) {
  1175. waitForChannelOpenFn(peersNonInterop[pni], null);
  1176. }
  1177. }
  1178. }
  1179.  
  1180. if (peersInterop.length > 0) {
  1181. if (self._hasMCU) {
  1182. startDataSessionFn('MCU', 'main', peersInterop);
  1183. } else {
  1184. for (var pi = 0; pi < peersInterop.length; pi++) {
  1185. startDataSessionFn(peersInterop[pi], 'main', null);
  1186. }
  1187. }
  1188. }
  1189. };
  1190.  
  1191. /**
  1192. * <blockquote class="info">
  1193. * Note that this feature is not supported by MCU enabled Peer connections.<br>
  1194. * To start data streaming session, see the <a href="#method_startStreamingData"><code>startStreamingData()</code>
  1195. * method</a>. To stop data streaming session, see the <a href="#method_stopStreamingData"><code>stopStreamingData()</code> method</a>
  1196. * </blockquote>
  1197. * Function that sends a data chunk from User to Peers for an existing active data streaming session.
  1198. * @method streamData
  1199. * @param {String} streamId The data streaming session ID.
  1200. * @param {String|Blob|ArrayBuffer} chunk The data chunk.
  1201. * <small>By default when it is not string data streaming, data chunks when is are expected to be
  1202. * sent in Blob or ArrayBuffer, and ArrayBuffer data chunks will be converted to Blob.</small>
  1203. * <small>For binary data chunks, the limit is <code>65456</code>.</small>
  1204. * <small>For string data chunks, the limit is <code>1212</code>.</small>
  1205. * @trigger <ol class="desc-seq">
  1206. * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
  1207. * <li>If Peer connection (or MCU Peer connection if enabled)
  1208. * data streaming Datachannel has not been opened: <small>This can be checked with
  1209. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  1210. * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
  1211. * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
  1212. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
  1213. * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  1214. * <li>Starts sending the data chunk to Peer. <ol>
  1215. * <li><a href="#event_incomingDataStream"><code>incomingDataStream</code> event</a> triggers.</li>
  1216. * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  1217. * triggers parameter payload <code>state</code> as <code>SENT</code>.</li>
  1218. * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  1219. * triggers parameter payload <code>state</code> as <code>RECEIVED</code>.</li></ol></li></ol>
  1220. * @example
  1221. * // Example 1: Start streaming
  1222. * var currentStreamId = null
  1223. * if (file.size > chunkLimit) {
  1224. * while ((file.size - 1) > endCount) {
  1225. * endCount = startCount + chunkLimit;
  1226. * chunks.push(file.slice(startCount, endCount));
  1227. * startCount += chunkLimit;
  1228. * }
  1229. * if ((file.size - (startCount + 1)) > 0) {
  1230. * chunks.push(file.slice(startCount, file.size - 1));
  1231. * }
  1232. * } else {
  1233. * chunks.push(file);
  1234. * }
  1235. * var processNextFn = function () {
  1236. * if (chunks.length > 0) {
  1237. * skylinkDemo.once("incomingDataStream", function () {
  1238. * setTimeout(processNextFn, 1);
  1239. * }, function (data, evtStreamId, evtPeerId, streamInfo, isSelf) {
  1240. * return isSelf && evtStreamId === currentStreamId;
  1241. * });
  1242. * var chunk = chunks[0];
  1243. * chunks.splice(0, 1);
  1244. * skylinkDemo.streamData(currentStreamId, chunk);
  1245. * } else {
  1246. * skylinkDemo.stopStreamingData(currentStreamId);
  1247. * }
  1248. * };
  1249. * skylinkDemo.once("incomingDataStreamStarted", processNextFn, function (streamId, peerId, streamInfo, isSelf) {
  1250. * currentStreamId = streamId;
  1251. * return isSelf;
  1252. * });
  1253. * skylinkDemo.once("incomingDataStreamStopped", function () {
  1254. * // Render file
  1255. * }, function (streamId, peerId, streamInfo, isSelf) {
  1256. * return currentStreamId === streamId && isSelf;
  1257. * });
  1258. * skylinkDemo.startStreamingData(false);
  1259. * @beta
  1260. * @for Skylink
  1261. * @since 0.6.18
  1262. */
  1263. Skylink.prototype.streamData = function(transferId, dataChunk) {
  1264. var self = this;
  1265.  
  1266. if (!(transferId && typeof transferId === 'string')) {
  1267. log.error('Failed streaming data chunk as stream session ID is not provided.');
  1268. return;
  1269. }
  1270.  
  1271. if (!(dataChunk && typeof dataChunk === 'object' && (dataChunk instanceof Blob || dataChunk instanceof ArrayBuffer))) {
  1272. log.error('Failed streaming data chunk as it is not provided.');
  1273. return;
  1274. }
  1275.  
  1276. if (!(self._inRoom && self._user && self._user.sid)) {
  1277. log.error('Failed streaming data chunk as User is not in the Room.');
  1278. return;
  1279. }
  1280.  
  1281. if (!self._dataStreams[transferId]) {
  1282. log.error('Failed streaming data chunk as session does not exists.');
  1283. return;
  1284. }
  1285.  
  1286. if (!self._dataStreams[transferId].isUpload) {
  1287. log.error('Failed streaming data chunk as session is not sending.');
  1288. return;
  1289. }
  1290.  
  1291. if (self._dataStreams[transferId].sessionChunkType === 'string' ? typeof dataChunk !== 'string' :
  1292. typeof dataChunk !== 'object') {
  1293. log.error('Failed streaming data chunk as data chunk does not match expected data type.');
  1294. return;
  1295. }
  1296.  
  1297. if (self._hasMCU) {
  1298. log.error('Failed streaming data chunk as MCU does not support this feature yet.');
  1299. return;
  1300. }
  1301.  
  1302. var updatedDataChunk = dataChunk instanceof ArrayBuffer ? new Blob(dataChunk) : dataChunk;
  1303.  
  1304. if (self._dataStreams[transferId].sessionChunkType === 'string' ? updatedDataChunk.length > self._CHUNK_DATAURL_SIZE :
  1305. updatedDataChunk.length > self._BINARY_FILE_SIZE) {
  1306. log.error('Failed streaming data chunk as data chunk exceeds maximum chunk limit.');
  1307. return;
  1308. }
  1309.  
  1310. var sessionInfo = {
  1311. chunk: updatedDataChunk,
  1312. chunkSize: updatedDataChunk.size || updatedDataChunk.length || updatedDataChunk.byteLength,
  1313. chunkType: self._dataStreams[transferId].sessionChunkType === 'string' ?
  1314. self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
  1315. isPrivate: self._dataStreams[transferId].sessionChunkType.isPrivate,
  1316. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  1317. senderPeerId: self._user && self._user.sid ? self._user.sid : null
  1318. };
  1319.  
  1320. var peersInterop = [];
  1321. var peersNonInterop = [];
  1322. var sendDataFn = function (peerId, channelProp, targetPeers) {
  1323. // When ready to be sent
  1324. var onSendDataFn = function (buffer) {
  1325. self._sendMessageToDataChannel(peerId, buffer, channelProp, true);
  1326.  
  1327. var updatedSessionInfo = clone(sessionInfo);
  1328. delete updatedSessionInfo.chunk;
  1329.  
  1330. if (targetPeers) {
  1331. for (var i = 0; i < targetPeers.length; i++) {
  1332. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENT, transferId, targetPeers[i], sessionInfo, null);
  1333. self._trigger('incomingDataStream', dataChunk, transferId, targetPeers[i], updatedSessionInfo, true);
  1334. }
  1335. } else {
  1336. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENT, transferId, peerId, sessionInfo, null);
  1337. self._trigger('incomingDataStream', dataChunk, transferId, peerId, updatedSessionInfo, true);
  1338. }
  1339. };
  1340.  
  1341. if (dataChunk instanceof Blob && sessionInfo.chunkType === self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
  1342. self._blobToArrayBuffer(dataChunk, onSendDataFn);
  1343. } else if (!(dataChunk instanceof Blob) && sessionInfo.chunkType === self.DATA_TRANSFER_DATA_TYPE.BLOB) {
  1344. onSendDataFn(new Blob([dataChunk]));
  1345. } else if (AdapterJS.webrtcDetectedType === 'plugin' && typeof dataChunk !== 'string') {
  1346. onSendDataFn(new Int8Array(dataChunk));
  1347. } else {
  1348. onSendDataFn(dataChunk);
  1349. }
  1350. };
  1351.  
  1352. for (var peerId in self._dataStreams[transferId].sessions) {
  1353. if (self._dataStreams[transferId].sessions.hasOwnProperty(peerId) && self._dataStreams[transferId].sessions[peerId]) {
  1354. var channelProp = self._dataStreams[transferId].sessions[peerId];
  1355.  
  1356. if (!(self._dataChannels[self._hasMCU ? 'MCU' : peerId] && self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp] &&
  1357. self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN &&
  1358. self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].streamId === transferId)) {
  1359. log.error([peerId, 'RTCDataChannel', transferId, 'Failed streaming data as it has not started or is ready.']);
  1360. self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo,
  1361. new Error('Streaming as it has not started or Datachannel connection is not open.'));
  1362. return;
  1363. }
  1364.  
  1365. if (self._hasMCU) {
  1366. if (channelProp === 'main') {
  1367. peersInterop.push(peerId);
  1368. } else {
  1369. peersNonInterop.push(peerId);
  1370. }
  1371. } else {
  1372. sendDataFn(peerId, channelProp);
  1373. }
  1374. }
  1375. }
  1376.  
  1377. if (self._hasMCU) {
  1378. if (peersInterop.length > 0) {
  1379. sendDataFn(peerId, 'main', peersInterop);
  1380. }
  1381. if (peersNonInterop.length > 0) {
  1382. sendDataFn(peerId, transferId, peersNonInterop);
  1383. }
  1384. }
  1385. };
  1386.  
  1387. /**
  1388. * <blockquote class="info">
  1389. * To start data streaming session, see the <a href="#method_startStreamingData"><code>startStreamingData()</code>
  1390. * method</a> To start streaming data, see the <a href="#method_streamData"><code>streamData()</code>
  1391. * method</a>.
  1392. * </blockquote>
  1393. * Function that stops a data chunks streaming session from User to Peers.
  1394. * @method stopStreamingData
  1395. * @param {String} streamId The data streaming session ID.
  1396. * @trigger <ol class="desc-seq">
  1397. * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
  1398. * <li>If Peer connection (or MCU Peer connection if enabled)
  1399. * data streaming Datachannel has not been opened: <small>This can be checked with
  1400. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  1401. * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
  1402. * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
  1403. * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
  1404. * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
  1405. * <li>Stops the data streaming session to Peer. <ol>
  1406. * <li><a href="#event_incomingDataStreamStopped"><code>incomingDataStreamStopped</code> event</a> triggers.</li>
  1407. * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  1408. * triggers parameter payload <code>state</code> as <code>SENDING_STOPPED</code>.</li>
  1409. * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
  1410. * triggers parameter payload <code>state</code> as <code>RECEIVING_STOPPED</code>.</li></ol></li></ol>
  1411. * @example
  1412. * skylinkDemo.stopStreamData(streamId);
  1413. * @beta
  1414. * @for Skylink
  1415. * @since 0.6.18
  1416. */
  1417. Skylink.prototype.stopStreamingData = function(transferId) {
  1418. var self = this;
  1419.  
  1420. if (!(transferId && typeof transferId === 'string')) {
  1421. log.error('Failed streaming data chunk as stream session ID is not provided.');
  1422. return;
  1423. }
  1424.  
  1425. if (!(self._inRoom && self._user && self._user.sid)) {
  1426. log.error('Failed streaming data chunk as User is not in the Room.');
  1427. return;
  1428. }
  1429.  
  1430. if (!self._dataStreams[transferId]) {
  1431. log.error('Failed stopping data streaming session as it does not exists.');
  1432. return;
  1433. }
  1434.  
  1435. if (!self._dataStreams[transferId].isUpload) {
  1436. log.error('Failed stopping data streaming session as it is not sending.');
  1437. return;
  1438. }
  1439.  
  1440. if (self._hasMCU) {
  1441. log.error('Failed stopping data streaming session as MCU does not support this feature yet.');
  1442. return;
  1443. }
  1444.  
  1445. var sessionInfo = {
  1446. chunk: null,
  1447. chunkSize: 0,
  1448. chunkType: self._dataStreams[transferId].sessionChunkType === 'string' ?
  1449. self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
  1450. isPrivate: self._dataStreams[transferId].sessionChunkType.isPrivate,
  1451. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  1452. senderPeerId: self._user && self._user.sid ? self._user.sid : null
  1453. };
  1454.  
  1455. var peersInterop = [];
  1456. var peersNonInterop = [];
  1457. var sendDataFn = function (peerId, channelProp, targetPeers) {
  1458. self._sendMessageToDataChannel(peerId, {
  1459. type: self._DC_PROTOCOL_TYPE.WRQ,
  1460. transferId: transferId,
  1461. name: transferId,
  1462. size: 0,
  1463. originalSize: 0,
  1464. dataType: 'fastBinaryStop',
  1465. mimeType: null,
  1466. chunkType: self._dataStreams[transferId].sessionChunkType,
  1467. chunkSize: 0,
  1468. timeout: 0,
  1469. isPrivate: self._dataStreams[transferId].isPrivate,
  1470. sender: self._user.sid,
  1471. agent: AdapterJS.webrtcDetectedBrowser,
  1472. version: AdapterJS.webrtcDetectedVersion,
  1473. target: targetPeers ? targetPeers : peerId
  1474. }, channelProp);
  1475.  
  1476. var updatedSessionInfo = clone(sessionInfo);
  1477. delete updatedSessionInfo.chunk;
  1478.  
  1479. if (targetPeers) {
  1480. for (var i = 0; i < targetPeers.length; i++) {
  1481. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STOPPED, transferId, targetPeers[i], sessionInfo, null);
  1482. self._trigger('incomingDataStreamStopped', transferId, targetPeers[i], updatedSessionInfo, true);
  1483. }
  1484. } else {
  1485. self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STOPPED, transferId, peerId, sessionInfo, null);
  1486. self._trigger('incomingDataStreamStopped', transferId, peerId, updatedSessionInfo, true);
  1487. }
  1488. };
  1489.  
  1490. for (var peerId in self._dataStreams[transferId].sessions) {
  1491. if (self._dataStreams[transferId].sessions.hasOwnProperty(peerId) && self._dataStreams[transferId].sessions[peerId]) {
  1492. var channelProp = self._dataStreams[transferId].sessions[peerId];
  1493.  
  1494. if (!(self._dataChannels[self._hasMCU ? 'MCU' : peerId] && self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp] &&
  1495. self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN &&
  1496. self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].streamId === transferId)) {
  1497. log.error([peerId, 'RTCDataChannel', transferId, 'Failed stopping data streaming session as channel is closed.']);
  1498. self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo,
  1499. new Error('Failed stopping data streaming session as Datachannel connection is not open or is active.'));
  1500. return;
  1501. }
  1502.  
  1503. if (self._hasMCU) {
  1504. if (self._dataStreams[transferId].sessions[peerId] === 'main') {
  1505. peersInterop.push(peerId);
  1506. } else {
  1507. peersNonInterop.push(peerId);
  1508. }
  1509. } else {
  1510. sendDataFn(peerId, channelProp);
  1511. }
  1512. }
  1513. }
  1514.  
  1515. if (self._hasMCU) {
  1516. if (peersInterop.length > 0) {
  1517. sendDataFn(peerId, 'main', peersInterop);
  1518. }
  1519. if (peersNonInterop.length > 0) {
  1520. sendDataFn(peerId, transferId, peersNonInterop);
  1521. }
  1522. }
  1523. };
  1524.  
  1525.  
  1526. /**
  1527. * Function that starts the data transfer to Peers.
  1528. * @method _startDataTransfer
  1529. * @private
  1530. * @for Skylink
  1531. * @since 0.6.1
  1532. */
  1533. Skylink.prototype._startDataTransfer = function(data, timeout, targetPeerId, sendChunksAsBinary, callback, sessionType) {
  1534. var self = this;
  1535. var transferId = (self._user ? self._user.sid : '') + '_' + (new Date()).getTime();
  1536. var transferErrors = {};
  1537. var transferCompleted = [];
  1538. var chunks = [];
  1539. var listOfPeers = Object.keys(self._peerConnections);
  1540. var sessionChunkType = 'string';
  1541. var transferInfo = {
  1542. name: null,
  1543. size: null,
  1544. chunkSize: null,
  1545. chunkType: null,
  1546. dataType: null,
  1547. mimeType: null,
  1548. direction: self.DATA_TRANSFER_TYPE.UPLOAD,
  1549. timeout: 60,
  1550. isPrivate: false,
  1551. percentage: 0
  1552. };
  1553.  
  1554. // sendBlobData(.., timeout)
  1555. if (typeof timeout === 'number') {
  1556. transferInfo.timeout = timeout;
  1557. } else if (Array.isArray(timeout)) {
  1558. listOfPeers = timeout;
  1559. } else if (timeout && typeof timeout === 'string') {
  1560. listOfPeers = [timeout];
  1561. } else if (timeout && typeof timeout === 'boolean') {
  1562. sessionChunkType = 'binary';
  1563. } else if (typeof timeout === 'function') {
  1564. callback = timeout;
  1565. }
  1566.  
  1567. // sendBlobData(.., .., targetPeerId)
  1568. if (Array.isArray(targetPeerId)) {
  1569. listOfPeers = targetPeerId;
  1570. } else if (targetPeerId && typeof targetPeerId === 'string') {
  1571. listOfPeers = [targetPeerId];
  1572. } else if (targetPeerId && typeof targetPeerId === 'boolean') {
  1573. sessionChunkType = 'binary';
  1574. } else if (typeof targetPeerId === 'function') {
  1575. callback = targetPeerId;
  1576. }
  1577.  
  1578. // sendBlobData(.., .., .., sendChunksAsBinary)
  1579. if (sendChunksAsBinary && typeof sendChunksAsBinary === 'boolean') {
  1580. sessionChunkType = 'binary';
  1581. } else if (typeof sendChunksAsBinary === 'function') {
  1582. callback = sendChunksAsBinary;
  1583. }
  1584.  
  1585. // Remove MCU Peer as list of Peers
  1586. // if (listOfPeers.indexOf('MCU') > -1) {
  1587. // listOfPeers.splice(listOfPeers.indexOf('MCU'), 1);
  1588. // }
  1589.  
  1590. // Function that returns the error emitted before data transfer has started
  1591. var emitErrorBeforeDataTransferFn = function (error) {
  1592. log.error(error);
  1593.  
  1594. if (typeof callback === 'function') {
  1595. var transferErrors = {};
  1596.  
  1597. if (listOfPeers.length === 0) {
  1598. transferErrors.self = new Error(error);
  1599. /*self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.START_ERROR, null, null, transferInfo, {
  1600. transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
  1601. message: new Error(error)
  1602. });*/
  1603. } else {
  1604. for (var i = 0; i < listOfPeers.length; i++) {
  1605. transferErrors[listOfPeers[i]] = new Error(error);
  1606. /*self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.START_ERROR, null, listOfPeers[i], transferInfo, {
  1607. transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
  1608. message: new Error(error)
  1609. });*/
  1610. }
  1611. }
  1612.  
  1613. callback({
  1614. transferId: null,
  1615. transferInfo: transferInfo,
  1616. listOfPeers: listOfPeers,
  1617. transferErrors: transferErrors
  1618. }, null);
  1619. }
  1620. };
  1621.  
  1622. if (sessionType === 'blob') {
  1623. if (self._hasMCU && sessionChunkType === 'binary') {
  1624. log.warn('Binary data chunks transfer is not yet supported with MCU environment. ' +
  1625. 'Fallbacking to binary string data chunks transfer.');
  1626. sessionChunkType = 'string';
  1627. }
  1628.  
  1629. var chunkSize = sessionChunkType === 'string' ? (AdapterJS.webrtcDetectedBrowser === 'firefox' ?
  1630. self._MOZ_CHUNK_FILE_SIZE : self._CHUNK_FILE_SIZE) : (AdapterJS.webrtcDetectedBrowser === 'firefox' ?
  1631. self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE);
  1632.  
  1633. transferInfo.dataType = self.DATA_TRANSFER_SESSION_TYPE.BLOB;
  1634. transferInfo.chunkSize = sessionChunkType === 'string' ? 4 * Math.ceil(chunkSize / 3) : chunkSize;
  1635. transferInfo.chunkType = sessionChunkType === 'binary' ? self._binaryChunkType : self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING;
  1636.  
  1637. // Start checking if data transfer can start
  1638. if (!(data && typeof data === 'object' && data instanceof Blob)) {
  1639. emitErrorBeforeDataTransferFn('Provided data is not a Blob data');
  1640. return;
  1641. }
  1642.  
  1643. transferInfo.name = data.name || transferId;
  1644. transferInfo.mimeType = data.type || null;
  1645.  
  1646. if (data.size < 1) {
  1647. emitErrorBeforeDataTransferFn('Provided data is not a valid Blob data.');
  1648. return;
  1649. }
  1650.  
  1651. transferInfo.originalSize = data.size;
  1652. transferInfo.size = sessionChunkType === 'string' ? 4 * Math.ceil(data.size / 3) : data.size;
  1653. chunks = self._chunkBlobData(data, chunkSize);
  1654. } else {
  1655. transferInfo.dataType = self.DATA_TRANSFER_SESSION_TYPE.DATA_URL;
  1656. transferInfo.chunkSize = self._CHUNK_DATAURL_SIZE;
  1657. transferInfo.chunkType = self.DATA_TRANSFER_DATA_TYPE.STRING;
  1658.  
  1659. // Start checking if data transfer can start
  1660. if (!(data && typeof data === 'string')) {
  1661. emitErrorBeforeDataTransferFn('Provided data is not a dataURL');
  1662. return;
  1663. }
  1664.  
  1665. transferInfo.originalSize = transferInfo.size = data.length || data.size;
  1666. chunks = self._chunkDataURL(data, transferInfo.chunkSize);
  1667. }
  1668.  
  1669. if (!(self._user && self._user.sid)) {
  1670. emitErrorBeforeDataTransferFn('Unable to send any ' +
  1671. sessionType.replace('data', 'dataURL') + ' data. User is not in Room.');
  1672. return;
  1673. }
  1674.  
  1675. if (!self._initOptions.enableDataChannel) {
  1676. emitErrorBeforeDataTransferFn('Unable to send any ' +
  1677. sessionType.replace('data', 'dataURL') + ' data. Datachannel is disabled');
  1678. return;
  1679. }
  1680.  
  1681. if (listOfPeers.length === 0) {
  1682. emitErrorBeforeDataTransferFn('Unable to send any ' +
  1683. sessionType.replace('data', 'dataURL') + ' data. There are no Peers to start data transfer with');
  1684. return;
  1685. }
  1686.  
  1687. self._dataTransfers[transferId] = clone(transferInfo);
  1688. self._dataTransfers[transferId].peers = {};
  1689. self._dataTransfers[transferId].peers.main = {};
  1690. self._dataTransfers[transferId].peers[transferId] = {};
  1691. self._dataTransfers[transferId].sessions = {};
  1692. self._dataTransfers[transferId].chunks = chunks;
  1693. self._dataTransfers[transferId].enforceBSPeers = [];
  1694. self._dataTransfers[transferId].enforcedBSInfo = {};
  1695. self._dataTransfers[transferId].sessionType = sessionType;
  1696. self._dataTransfers[transferId].sessionChunkType = sessionChunkType;
  1697. self._dataTransfers[transferId].senderPeerId = self._user.sid;
  1698.  
  1699. // Check if fallback chunks is required
  1700. if (sessionType === 'blob' && sessionChunkType === 'binary') {
  1701. for (var p = 0; p < listOfPeers.length; p++) {
  1702. var protocolVer = (((self._peerInformations[listOfPeers[p]]) || {}).agent || {}).DTProtocolVersion || '0.1.0';
  1703.  
  1704. // C++ SDK does not support binary file transfer for now
  1705. if (self._isLowerThanVersion(protocolVer, '0.1.3')) {
  1706. self._dataTransfers[transferId].enforceBSPeers.push(listOfPeers[p]);
  1707. }
  1708. }
  1709.  
  1710. if (self._dataTransfers[transferId].enforceBSPeers.length > 0) {
  1711. var bsChunkSize = AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_CHUNK_FILE_SIZE : self._CHUNK_FILE_SIZE;
  1712. var bsChunks = self._chunkBlobData(new Blob(chunks), bsChunkSize);
  1713.  
  1714. self._dataTransfers[transferId].enforceBSInfo = {
  1715. chunkSize: 4 * Math.ceil(bsChunkSize / 3),
  1716. chunkType: self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
  1717. size: 4 * Math.ceil(transferInfo.originalSize / 3),
  1718. chunks: bsChunks
  1719. };
  1720. }
  1721. }
  1722.  
  1723. /**
  1724. * Complete Peer function.
  1725. */
  1726. var completeFn = function (peerId, error) {
  1727. // Ignore if already added.
  1728. if (transferCompleted.indexOf(peerId) > -1) {
  1729. return;
  1730. }
  1731.  
  1732. log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer result. Is errors present? ->'], error);
  1733.  
  1734. transferCompleted.push(peerId);
  1735.  
  1736. if (error) {
  1737. transferErrors[peerId] = new Error(error);
  1738. }
  1739.  
  1740. if (listOfPeers.length === transferCompleted.length) {
  1741. log.log([null, 'RTCDataChannel', transferId, 'Data transfer request completed']);
  1742.  
  1743. if (typeof callback === 'function') {
  1744. if (Object.keys(transferErrors).length > 0) {
  1745. callback({
  1746. transferId: transferId,
  1747. transferInfo: self._getTransferInfo(transferId, peerId, false, true, false),
  1748. transferErrors: transferErrors,
  1749. listOfPeers: listOfPeers
  1750. }, null);
  1751. } else {
  1752. callback(null, {
  1753. transferId: transferId,
  1754. transferInfo: self._getTransferInfo(transferId, peerId, false, true, false),
  1755. listOfPeers: listOfPeers
  1756. });
  1757. }
  1758. }
  1759. }
  1760. };
  1761.  
  1762. for (var i = 0; i < listOfPeers.length; i++) {
  1763. var MCUInteropStatus = self._startDataTransferToPeer(transferId, listOfPeers[i], completeFn, null, null);
  1764.  
  1765. if (typeof MCUInteropStatus === 'boolean') {
  1766. if (MCUInteropStatus === true) {
  1767. self._dataTransfers[transferId].peers.main[listOfPeers[i]] = true;
  1768. } else {
  1769. self._dataTransfers[transferId].peers[transferId][listOfPeers[i]] = true;
  1770. }
  1771. }
  1772. }
  1773.  
  1774. if (self._hasMCU) {
  1775. if (Object.keys(self._dataTransfers[transferId].peers.main).length > 0) {
  1776. self._startDataTransferToPeer(transferId, 'MCU', completeFn, 'main',
  1777. Object.keys(self._dataTransfers[transferId].peers.main));
  1778. }
  1779.  
  1780. if (Object.keys(self._dataTransfers[transferId].peers[transferId]).length > 0) {
  1781. self._startDataTransferToPeer(transferId, 'MCU', completeFn, transferId,
  1782. Object.keys(self._dataTransfers[transferId].peers[transferId]));
  1783. }
  1784. }
  1785. };
  1786.  
  1787. /**
  1788. * Function that starts or listens the data transfer status to Peer.
  1789. * This reacts differently during MCU environment.
  1790. * @method _startDataTransferToPeer
  1791. * @return {Boolean} Returns a Boolean only during MCU environment which flag indicates if Peer requires interop
  1792. * (Use messaging Datachannel connection instead).
  1793. * @private
  1794. * @since 0.6.16
  1795. */
  1796. Skylink.prototype._startDataTransferToPeer = function (transferId, peerId, callback, channelProp, targetPeers) {
  1797. var self = this;
  1798. var peerConnectionStateCbFn = null;
  1799. var dataChannelStateCbFn = null;
  1800.  
  1801. /**
  1802. * Emit event for Peers function.
  1803. */
  1804. var emitEventFn = function (cb) {
  1805. var peers = targetPeers || [peerId];
  1806. for (var i = 0; i < peers.length; i++) {
  1807. cb(peers[i]);
  1808. }
  1809. };
  1810.  
  1811. /**
  1812. * Return error and trigger them if failed before or during data transfers function.
  1813. */
  1814. var returnErrorBeforeTransferFn = function (error) {
  1815. // Replace if it is a MCU Peer errors for clear indication in error message
  1816. var updatedError = peerId === 'MCU' ? error.replace(/Peer/g, 'MCU Peer') : error;
  1817.  
  1818. emitEventFn(function (evtPeerId) {
  1819. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, evtPeerId,
  1820. self._getTransferInfo(transferId, peerId, true, true, false), {
  1821. message: new Error(updatedError),
  1822. transferType: self.DATA_TRANSFER_TYPE.UPLOAD
  1823. });
  1824. });
  1825. };
  1826.  
  1827. /**
  1828. * Send WRQ protocol to start data transfers.
  1829. */
  1830. var sendWRQFn = function () {
  1831. var size = self._dataTransfers[transferId].size;
  1832. var chunkSize = self._dataTransfers[transferId].chunkSize;
  1833. var chunkType = self._dataTransfers[transferId].sessionChunkType;
  1834.  
  1835. if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1) {
  1836. log.warn([peerId, 'RTCDataChannel', transferId,
  1837. 'Binary data chunks transfer is not yet supported with Peer connecting from ' +
  1838. 'Android, iOS and C++ SDK. Fallbacking to binary string data chunks transfer.']);
  1839.  
  1840. size = self._dataTransfers[transferId].enforceBSInfo.size;
  1841. chunkSize = self._dataTransfers[transferId].enforceBSInfo.chunkSize;
  1842. chunkType = 'string';
  1843. }
  1844.  
  1845. self._sendMessageToDataChannel(peerId, {
  1846. type: self._DC_PROTOCOL_TYPE.WRQ,
  1847. transferId: transferId,
  1848. name: self._dataTransfers[transferId].name,
  1849. size: size,
  1850. originalSize: self._dataTransfers[transferId].originalSize,
  1851. dataType: self._dataTransfers[transferId].sessionType,
  1852. mimeType: self._dataTransfers[transferId].mimeType,
  1853. chunkType: chunkType,
  1854. chunkSize: chunkSize,
  1855. timeout: self._dataTransfers[transferId].timeout,
  1856. isPrivate: self._dataTransfers[transferId].isPrivate,
  1857. sender: self._user.sid,
  1858. agent: AdapterJS.webrtcDetectedBrowser,
  1859. version: AdapterJS.webrtcDetectedVersion,
  1860. target: targetPeers ? targetPeers : peerId
  1861. }, channelProp);
  1862.  
  1863. emitEventFn(function (evtPeerId) {
  1864. self._trigger('incomingDataRequest', transferId, evtPeerId,
  1865. self._getTransferInfo(transferId, peerId, false, false, false), true);
  1866.  
  1867. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.USER_UPLOAD_REQUEST, transferId, evtPeerId,
  1868. self._getTransferInfo(transferId, peerId, true, false, false), null);
  1869. });
  1870. };
  1871.  
  1872. // Listen to data transfer state
  1873. if (peerId !== 'MCU') {
  1874. var dataTransferStateCbFn = function (state, evtTransferId, evtPeerId, transferInfo, error) {
  1875. if (peerConnectionStateCbFn) {
  1876. self.off('peerConnectionState', peerConnectionStateCbFn);
  1877. }
  1878.  
  1879. if (dataChannelStateCbFn) {
  1880. self.off('dataChannelState', dataChannelStateCbFn);
  1881. }
  1882.  
  1883. if (channelProp) {
  1884. delete self._dataTransfers[transferId].peers[channelProp][peerId];
  1885. }
  1886.  
  1887. callback(peerId, state === self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED ? null :
  1888. error.message.message || error.message.toString());
  1889.  
  1890. delete self._dataTransfers[transferId].sessions[peerId];
  1891.  
  1892. if (self._hasMCU && Object.keys(self._dataTransfers[transferId].peers.main).length === 0 &&
  1893. self._dataChannels.MCU && self._dataChannels.MCU.main) {
  1894. self._dataChannels.MCU.main.transferId = null;
  1895.  
  1896. } else if (channelProp === 'main' && self._dataChannels[peerId] && self._dataChannels[peerId].main) {
  1897. self._dataChannels[peerId].main.transferId = null;
  1898. }
  1899.  
  1900. if (Object.keys(self._dataTransfers[transferId].sessions).length === 0) {
  1901. delete self._dataTransfers[transferId];
  1902. }
  1903. };
  1904.  
  1905. self.once('dataTransferState', dataTransferStateCbFn, function (state, evtTransferId, evtPeerId) {
  1906. if (!(self._dataTransfers[transferId] && (self._hasMCU ?
  1907. (self._dataTransfers[transferId].peers.main[peerId] || self._dataTransfers[transferId].peers[transferId][peerId]) :
  1908. self._dataTransfers[transferId].sessions[peerId]))) {
  1909.  
  1910. if (dataTransferStateCbFn) {
  1911. self.off('dataTransferState', dataTransferStateCbFn);
  1912. }
  1913.  
  1914. if (peerConnectionStateCbFn) {
  1915. self.off('peerConnectionState', peerConnectionStateCbFn);
  1916. }
  1917.  
  1918. if (dataChannelStateCbFn) {
  1919. self.off('dataChannelState', dataChannelStateCbFn);
  1920. }
  1921. return;
  1922. }
  1923.  
  1924. return evtTransferId === transferId && evtPeerId === peerId && [
  1925. self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED,
  1926. self.DATA_TRANSFER_STATE.ERROR,
  1927. self.DATA_TRANSFER_STATE.CANCEL,
  1928. self.DATA_TRANSFER_STATE.REJECTED].indexOf(state) > -1;
  1929. });
  1930. }
  1931.  
  1932. // When Peer connection does not exists
  1933. if (!self._peerConnections[peerId]) {
  1934. returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection does not exists.');
  1935. return;
  1936. }
  1937.  
  1938. // When Peer session does not exists
  1939. if (!self._peerInformations[peerId]) {
  1940. returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection does not exists.');
  1941. return;
  1942. }
  1943.  
  1944. // When Peer connection is not STABLE
  1945. if (self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
  1946. returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection is not stable.');
  1947. return;
  1948. }
  1949.  
  1950. if (!self._dataTransfers[transferId]) {
  1951. returnErrorBeforeTransferFn('Unable to start data transfer as data transfer session is not in order.');
  1952. return;
  1953. }
  1954.  
  1955. if (!(self._dataChannels[peerId] && self._dataChannels[peerId].main)) {
  1956. returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel connection does not exists.');
  1957. return;
  1958. }
  1959.  
  1960. if (self._dataChannels[peerId].main.channel.readyState !== self.DATA_CHANNEL_STATE.OPEN) {
  1961. returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel connection is not opened.');
  1962. return;
  1963. }
  1964.  
  1965. var streamId = self._dataChannels[peerId].main.streamId;
  1966.  
  1967. if (streamId && channelProp === 'main' && self._dataStreams[streamId] &&
  1968. // Check if session chunk streaming is string and sending is string for Peer
  1969. ((self._dataStreams[streamId].sessionChunkType === 'string' &&
  1970. (self._dataTransfers[transferId].sessionChunkType === 'string' ||
  1971. self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1)) ||
  1972. // Check if session chunk streaming is binary and sending is binary for Peer
  1973. (self._dataStreams[streamId].sessionChunkType === 'binary' &&
  1974. self._dataStreams[streamId].sessionChunkType === 'binary' &&
  1975. self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) === -1))) {
  1976. returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel currently has an active ' +
  1977. self._dataStreams[streamId].sessionChunkType + ' data streaming session.');
  1978. return;
  1979. }
  1980.  
  1981. var protocolVer = (self._peerInformations[peerId].agent || {}).DTProtocolVersion || '0.1.0';
  1982. var requireInterop = self._isLowerThanVersion(protocolVer, '0.1.2') || !self._initOptions.enableSimultaneousTransfers;
  1983.  
  1984. // Prevent DATA_URL (or "string" dataType transfers) with Android / iOS / C++ SDKs
  1985. if (self._isLowerThanVersion(protocolVer, '0.1.2') && self._dataTransfers[transferId].sessionType === 'data' &&
  1986. self._dataTransfers[transferId].sessionChunkType === 'string') {
  1987. returnErrorBeforeTransferFn('Unable to start data transfer as Peer do not support DATA_URL type of data transfers');
  1988. return;
  1989. }
  1990.  
  1991. // Listen to Peer connection state for MCU Peer
  1992. if (peerId !== 'MCU' && self._hasMCU) {
  1993. channelProp = requireInterop ? 'main' : transferId;
  1994.  
  1995. peerConnectionStateCbFn = function () {
  1996. returnErrorBeforeTransferFn('Data transfer terminated as Peer connection is not stable.');
  1997. };
  1998.  
  1999. self.once('peerConnectionState', peerConnectionStateCbFn, function (state, evtPeerId) {
  2000. if (!self._dataTransfers[transferId]) {
  2001. self.off('peerConnectionState', peerConnectionStateCbFn);
  2002. return;
  2003. }
  2004. return state !== self.PEER_CONNECTION_STATE.STABLE && evtPeerId === peerId;
  2005. });
  2006. return requireInterop;
  2007. }
  2008.  
  2009. if (requireInterop || channelProp === 'main') {
  2010. // When MCU Datachannel connection has a transfer in-progress
  2011. if (self._dataChannels[peerId].main.transferId) {
  2012. returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel has a data transfer in-progress.');
  2013. return;
  2014. }
  2015. }
  2016.  
  2017. self._dataTransfers[transferId].sessions[peerId] = {
  2018. timer: null,
  2019. ackN: 0
  2020. };
  2021.  
  2022. dataChannelStateCbFn = function (state, evtPeerId, error) {
  2023. // Prevent from triggering in instances where the ackN === chunks.length
  2024. if (self._dataTransfers[transferId].sessions[peerId].ackN >= (self._dataTransfers[transferId].chunks.length - 1)) {
  2025. return;
  2026. }
  2027.  
  2028. if (error) {
  2029. returnErrorBeforeTransferFn(error.message || error.toString());
  2030. } else {
  2031. returnErrorBeforeTransferFn('Data transfer terminated as Peer Datachannel connection closed abruptly.');
  2032. }
  2033. };
  2034.  
  2035. self.once('dataChannelState', dataChannelStateCbFn, function (state, evtPeerId, error, channelName, channelType) {
  2036. if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2037. self.off('dataChannelState', dataChannelStateCbFn);
  2038. return;
  2039. }
  2040.  
  2041. if (!(evtPeerId === peerId && (channelProp === 'main' ?
  2042. channelType === self.DATA_CHANNEL_TYPE.MESSAGING : channelName === transferId))) {
  2043. return;
  2044. }
  2045.  
  2046. if (state === self.DATA_CHANNEL_STATE.OPEN && channelProp !== 'main' && channelName === transferId) {
  2047. self._dataChannels[peerId][channelProp].transferId = transferId;
  2048. sendWRQFn();
  2049. return false;
  2050. }
  2051.  
  2052. return [
  2053. self.DATA_CHANNEL_STATE.CREATE_ERROR,
  2054. self.DATA_CHANNEL_STATE.ERROR,
  2055. self.DATA_CHANNEL_STATE.CLOSING,
  2056. self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1;
  2057. });
  2058.  
  2059. // Create new Datachannel for Peer to start data transfer
  2060. if (!((requireInterop && peerId !== 'MCU') || channelProp === 'main')) {
  2061. channelProp = transferId;
  2062. self._createDataChannel(peerId, transferId, self._dataTransfers[transferId].sessionType === 'data' ?
  2063. self._CHUNK_DATAURL_SIZE : (self._dataTransfers[transferId].sessionChunkType === 'string' ?
  2064. (AdapterJS.webrtcDetectedBrowser === 'firefox' ? 16384 : 65546) : // After conversion to base64 string computed size
  2065. (AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE)));
  2066. } else {
  2067. channelProp = 'main';
  2068. self._dataChannels[peerId].main.transferId = transferId;
  2069. sendWRQFn();
  2070. }
  2071. };
  2072.  
  2073. /**
  2074. * Function that returns the data transfer session.
  2075. * @method _getTransferInfo
  2076. * @private
  2077. * @for Skylink
  2078. * @since 0.6.16
  2079. */
  2080. Skylink.prototype._getTransferInfo = function (transferId, peerId, returnDataProp, hidePercentage, returnDataAtStart) {
  2081. if (!this._dataTransfers[transferId]) {
  2082. return {};
  2083. }
  2084.  
  2085. var transferInfo = {
  2086. name: this._dataTransfers[transferId].name,
  2087. size: this._dataTransfers[transferId].size,
  2088. dataType: this._dataTransfers[transferId].dataType || this.DATA_TRANSFER_SESSION_TYPE.BLOB,
  2089. mimeType: this._dataTransfers[transferId].mimeType || null,
  2090. chunkSize: this._dataTransfers[transferId].chunkSize,
  2091. chunkType: this._dataTransfers[transferId].chunkType,
  2092. timeout: this._dataTransfers[transferId].timeout,
  2093. isPrivate: this._dataTransfers[transferId].isPrivate,
  2094. direction: this._dataTransfers[transferId].direction
  2095. };
  2096.  
  2097. if (this._dataTransfers[transferId].originalSize) {
  2098. transferInfo.size = this._dataTransfers[transferId].originalSize;
  2099.  
  2100. } else if (this._dataTransfers[transferId].chunkType === this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
  2101. transferInfo.size = Math.ceil(transferInfo.size * 3 / 4);
  2102. }
  2103.  
  2104. if (!hidePercentage) {
  2105. transferInfo.percentage = 0;
  2106.  
  2107. if (!this._dataTransfers[transferId].sessions[peerId]) {
  2108. if (returnDataProp) {
  2109. transferInfo.data = null;
  2110. }
  2111. return transferInfo;
  2112. }
  2113.  
  2114. if (this._dataTransfers[transferId].direction === this.DATA_TRANSFER_TYPE.DOWNLOAD) {
  2115. if (this._dataTransfers[transferId].sessions[peerId].receivedSize === this._dataTransfers[transferId].sessions[peerId].size) {
  2116. transferInfo.percentage = 100;
  2117.  
  2118. } else {
  2119. transferInfo.percentage = parseFloat(((this._dataTransfers[transferId].sessions[peerId].receivedSize /
  2120. this._dataTransfers[transferId].size) * 100).toFixed(2), 10);
  2121. }
  2122. } else {
  2123. var chunksLength = (this._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1 ?
  2124. this._dataTransfers[transferId].enforceBSInfo.chunks.length : this._dataTransfers[transferId].chunks.length);
  2125.  
  2126. if (this._dataTransfers[transferId].sessions[peerId].ackN === chunksLength) {
  2127. transferInfo.percentage = 100;
  2128.  
  2129. } else {
  2130. transferInfo.percentage = parseFloat(((this._dataTransfers[transferId].sessions[peerId].ackN /
  2131. chunksLength) * 100).toFixed(2), 10);
  2132. }
  2133. }
  2134.  
  2135. if (returnDataProp) {
  2136. if (typeof returnDataAtStart !== 'number') {
  2137. if (transferInfo.percentage === 100) {
  2138. transferInfo.data = this._getTransferData(transferId);
  2139. } else {
  2140. transferInfo.data = null;
  2141. }
  2142. } else {
  2143. transferInfo.percentage = returnDataAtStart;
  2144.  
  2145. if (returnDataAtStart === 0) {
  2146. transferInfo.data = this._getTransferData(transferId);
  2147. }
  2148. }
  2149. }
  2150. }
  2151.  
  2152. return transferInfo;
  2153. };
  2154.  
  2155. /**
  2156. * Function that returns the compiled data transfer data.
  2157. * @method _getTransferData
  2158. * @private
  2159. * @for Skylink
  2160. * @since 0.6.16
  2161. */
  2162. Skylink.prototype._getTransferData = function (transferId) {
  2163. if (!this._dataTransfers[transferId]) {
  2164. return null;
  2165. }
  2166.  
  2167. if (this._dataTransfers[transferId].dataType === this.DATA_TRANSFER_SESSION_TYPE.BLOB) {
  2168. var mimeType = {
  2169. name: this._dataTransfers[transferId].name
  2170. };
  2171.  
  2172. if (this._dataTransfers[transferId].mimeType) {
  2173. mimeType.type = this._dataTransfers[transferId].mimeType;
  2174. }
  2175.  
  2176. return new Blob(this._dataTransfers[transferId].chunks, mimeType);
  2177. }
  2178.  
  2179. return this._dataTransfers[transferId].chunks.join('');
  2180. };
  2181.  
  2182. /**
  2183. * Function that handles the data transfers sessions timeouts.
  2184. * @method _handleDataTransferTimeoutForPeer
  2185. * @private
  2186. * @for Skylink
  2187. * @since 0.6.16
  2188. */
  2189. Skylink.prototype._handleDataTransferTimeoutForPeer = function (transferId, peerId, setPeerTO) {
  2190. var self = this;
  2191.  
  2192. if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2193. log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer does not exists for Peer. Ignoring timeout.']);
  2194. return;
  2195. }
  2196.  
  2197. log.debug([peerId, 'RTCDataChannel', transferId, 'Clearing data transfer timer for Peer.']);
  2198.  
  2199. if (self._dataTransfers[transferId].sessions[peerId].timer) {
  2200. clearTimeout(self._dataTransfers[transferId].sessions[peerId].timer);
  2201. }
  2202.  
  2203. self._dataTransfers[transferId].sessions[peerId].timer = null;
  2204.  
  2205. if (setPeerTO) {
  2206. log.debug([peerId, 'RTCDataChannel', transferId, 'Setting data transfer timer for Peer.']);
  2207.  
  2208. self._dataTransfers[transferId].sessions[peerId].timer = setTimeout(function () {
  2209. if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2210. log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer already ended for Peer. Ignoring expired timeout.']);
  2211. return;
  2212. }
  2213.  
  2214. if (!(self._user && self._user.sid)) {
  2215. log.debug([peerId, 'RTCDataChannel', transferId, 'User is not in Room. Ignoring expired timeout.']);
  2216. return;
  2217. }
  2218.  
  2219. if (!self._dataChannels[peerId]) {
  2220. log.debug([peerId, 'RTCDataChannel', transferId, 'Datachannel connection does not exists. Ignoring expired timeout.']);
  2221. return;
  2222. }
  2223.  
  2224. log.error([peerId, 'RTCDataChannel', transferId, 'Data transfer response has timed out.']);
  2225.  
  2226. /**
  2227. * Emit event for Peers function.
  2228. */
  2229. var emitEventFn = function (cb) {
  2230. if (peerId === 'MCU') {
  2231. var broadcastedPeers = [self._dataTransfers[transferId].peers.main,
  2232. self._dataTransfers[transferId].peers[transferId]];
  2233.  
  2234. for (var i = 0; i < broadcastedPeers.length; i++) {
  2235. // Should not happen but insanity check
  2236. if (!broadcastedPeers[i]) {
  2237. continue;
  2238. }
  2239.  
  2240. for (var bcPeerId in broadcastedPeers[i]) {
  2241. if (broadcastedPeers[i].hasOwnProperty(bcPeerId) && broadcastedPeers[i][bcPeerId]) {
  2242. cb(bcPeerId);
  2243. }
  2244. }
  2245. }
  2246. } else {
  2247. cb(peerId);
  2248. }
  2249. };
  2250.  
  2251. var errorMsg = 'Connection Timeout. Longer than ' + self._dataTransfers[transferId].timeout +
  2252. ' seconds. Connection is abolished.';
  2253.  
  2254. self._sendMessageToDataChannel(peerId, {
  2255. type: self._DC_PROTOCOL_TYPE.ERROR,
  2256. content: errorMsg,
  2257. isUploadError: self._dataTransfers[transferId].direction === self.DATA_TRANSFER_TYPE.UPLOAD,
  2258. sender: self._user.sid,
  2259. name: self._dataTransfers[transferId].name
  2260. }, self._dataChannels[peerId][transferId] ? transferId : 'main');
  2261.  
  2262. emitEventFn(function (evtPeerId) {
  2263. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, peerId,
  2264. self._getTransferInfo(transferId, peerId, true, false, false), {
  2265. transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
  2266. message: new Error(errorMsg)
  2267. });
  2268. });
  2269. }, self._dataTransfers[transferId].timeout * 1000);
  2270. }
  2271. };
  2272.  
  2273. /**
  2274. * Function that handles the data received from Datachannel and
  2275. * routes to the relevant data transfer protocol handler.
  2276. * @method _processDataChannelData
  2277. * @private
  2278. * @for Skylink
  2279. * @since 0.6.16
  2280. */
  2281. Skylink.prototype._processDataChannelData = function(rawData, peerId, channelName, channelType) {
  2282. var self = this;
  2283. var transferId = null;
  2284. var streamId = null;
  2285. var isStreamChunk = false;
  2286. var channelProp = channelType === self.DATA_CHANNEL_TYPE.MESSAGING ? 'main' : channelName;
  2287.  
  2288. // Safe access of _dataChannel object in case dataChannel has been closed unexpectedly | ESS-983
  2289. var objPeerDataChannel = self._dataChannels[peerId] || {};
  2290. if (objPeerDataChannel.hasOwnProperty(channelProp) && typeof objPeerDataChannel[channelProp] === 'object') {
  2291. transferId = objPeerDataChannel[channelProp].transferId;
  2292. streamId = objPeerDataChannel[channelProp].streamId;
  2293. }
  2294. else {
  2295. return; // dataChannel not avaialble propbably having being closed abruptly | ESS-983
  2296. }
  2297.  
  2298. if (streamId && self._dataStreams[streamId]) {
  2299. isStreamChunk = self._dataStreams[streamId].sessionChunkType === 'string' ? typeof rawData === 'string' :
  2300. typeof rawData === 'object';
  2301. }
  2302.  
  2303. if (!self._peerConnections[peerId]) {
  2304. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping data received from Peer ' +
  2305. 'as connection is not present ->'], rawData);
  2306. return;
  2307. }
  2308.  
  2309. if (!(self._dataChannels[peerId] && self._dataChannels[peerId][channelProp])) {
  2310. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping data received from Peer ' +
  2311. 'as Datachannel connection is not present ->'], rawData);
  2312. return;
  2313. }
  2314.  
  2315. // Expect as string
  2316. if (typeof rawData === 'string') {
  2317. try {
  2318. var protocolData = JSON.parse(rawData);
  2319. isStreamChunk = false;
  2320.  
  2321. log.debug([peerId, 'RTCDataChannel', channelProp, 'Received protocol "' + protocolData.type + '" message ->'], protocolData);
  2322.  
  2323. // Ignore ACK, ERROR and CANCEL if there is no data transfer session in-progress
  2324. if ([self._DC_PROTOCOL_TYPE.ACK, self._DC_PROTOCOL_TYPE.ERROR, self._DC_PROTOCOL_TYPE.CANCEL].indexOf(protocolData.type) > -1 &&
  2325. !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2326. log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded protocol message as data transfer session ' +
  2327. 'is not present ->'], protocolData);
  2328. return;
  2329. }
  2330.  
  2331. switch (protocolData.type) {
  2332. case self._DC_PROTOCOL_TYPE.WRQ:
  2333. // Discard iOS bidirectional upload when Datachannel is in-progress for data transfers
  2334. if (transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId]) {
  2335. log.warn([peerId, 'RTCDataChannel', channelProp, 'Rejecting bidirectional data transfer request as ' +
  2336. 'it is currently not supported in the SDK ->'], protocolData);
  2337.  
  2338. self._sendMessageToDataChannel(peerId, {
  2339. type: self._DC_PROTOCOL_TYPE.ACK,
  2340. ackN: -1,
  2341. sender: self._user.sid
  2342. }, channelProp);
  2343. return;
  2344. }
  2345. self._WRQProtocolHandler(peerId, protocolData, channelProp);
  2346. break;
  2347. case self._DC_PROTOCOL_TYPE.ACK:
  2348. self._ACKProtocolHandler(peerId, protocolData, channelProp);
  2349. break;
  2350. case self._DC_PROTOCOL_TYPE.ERROR:
  2351. self._ERRORProtocolHandler(peerId, protocolData, channelProp);
  2352. break;
  2353. case self._DC_PROTOCOL_TYPE.CANCEL:
  2354. self._CANCELProtocolHandler(peerId, protocolData, channelProp);
  2355. break;
  2356. case self._DC_PROTOCOL_TYPE.MESSAGE:
  2357. self._MESSAGEProtocolHandler(peerId, protocolData, channelProp);
  2358. break;
  2359. default:
  2360. log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded unknown "' + protocolData.type + '" message ->'], protocolData);
  2361. }
  2362.  
  2363. } catch (error) {
  2364. if (rawData.indexOf('{') > -1 && rawData.indexOf('}') > 0) {
  2365. log.error([peerId, 'RTCDataChannel', channelProp, 'Failed parsing protocol step data error ->'], {
  2366. data: rawData,
  2367. error: error
  2368. });
  2369.  
  2370. self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error, channelName,
  2371. channelType, null, self._getDataChannelBuffer(peerId, channelProp));
  2372. throw error;
  2373. }
  2374.  
  2375. if (!isStreamChunk && !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2376. log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded data chunk without session ->'], rawData);
  2377. return;
  2378. }
  2379.  
  2380. if (!isStreamChunk && transferId) {
  2381. if (self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN]) {
  2382. log.warn([peerId, 'RTCDataChannel', transferId, 'Dropping data chunk ' + (!isStreamChunk ? '@' +
  2383. self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' as it has already been added ->'], rawData);
  2384. return;
  2385. }
  2386. }
  2387.  
  2388. var chunkType = self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING;
  2389.  
  2390. if (!isStreamChunk ? self._dataTransfers[transferId].dataType === self.DATA_TRANSFER_SESSION_TYPE.DATA_URL : true) {
  2391. log.debug([peerId, 'RTCDataChannel', channelProp, 'Received string data chunk ' + (!isStreamChunk ? '@' +
  2392. self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' with size ->'], rawData.length || rawData.size);
  2393.  
  2394. self._DATAProtocolHandler(peerId, rawData, self.DATA_TRANSFER_DATA_TYPE.STRING,
  2395. rawData.length || rawData.size || 0, channelProp);
  2396.  
  2397. } else {
  2398. var removeSpaceData = rawData.replace(/\s|\r|\n/g, '');
  2399.  
  2400. log.debug([peerId, 'RTCDataChannel', channelProp, 'Received binary string data chunk @' +
  2401. self._dataTransfers[transferId].sessions[peerId].ackN + ' with size ->'],
  2402. removeSpaceData.length || removeSpaceData.size);
  2403.  
  2404. self._DATAProtocolHandler(peerId, self._base64ToBlob(removeSpaceData), self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
  2405. removeSpaceData.length || removeSpaceData.size || 0, channelProp);
  2406. }
  2407. }
  2408. } else {
  2409. if (!isStreamChunk && !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
  2410. log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded data chunk without session ->'], rawData);
  2411. return;
  2412. }
  2413.  
  2414. if (!isStreamChunk && transferId) {
  2415. if (self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN]) {
  2416. log.warn([peerId, 'RTCDataChannel', transferId, 'Dropping data chunk ' + (!isStreamChunk ? '@' +
  2417. self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' as it has already been added ->'], rawData);
  2418. return;
  2419. }
  2420. }
  2421.  
  2422. if (rawData instanceof Blob) {
  2423. log.debug([peerId, 'RTCDataChannel', channelProp, 'Received blob data chunk ' + (isStreamChunk ? '' :
  2424. '@' + self._dataTransfers[transferId].sessions[peerId].ackN) + ' with size ->'], rawData.size);
  2425.  
  2426. self._DATAProtocolHandler(peerId, rawData, self.DATA_TRANSFER_DATA_TYPE.BLOB, rawData.size, channelProp);
  2427.  
  2428. } else {
  2429. var byteArray = rawData;
  2430. var blob = null;
  2431.  
  2432. // Plugin binary handling
  2433. if (rawData.constructor && rawData.constructor.name === 'Array') {
  2434. // Need to re-parse on some browsers
  2435. byteArray = new Int8Array(rawData);
  2436. }
  2437.  
  2438. // Fallback for older IE versions
  2439. if (AdapterJS.webrtcDetectedBrowser === 'IE') {
  2440. if (window.BlobBuilder) {
  2441. var bb = new BlobBuilder();
  2442. bb.append(rawData.constructor && rawData.constructor.name === 'ArrayBuffer' ?
  2443. byteArray : (new Uint8Array(byteArray)).buffer);
  2444. blob = bb.getBlob();
  2445. }
  2446. } else {
  2447. blob = new Blob([byteArray]);
  2448. }
  2449.  
  2450. log.debug([peerId, 'RTCDataChannel', channelProp, 'Received arraybuffer data chunk ' + (isStreamChunk ? '' :
  2451. '@' + self._dataTransfers[transferId].sessions[peerId].ackN) + ' with size ->'], blob.size);
  2452.  
  2453. self._DATAProtocolHandler(peerId, blob, self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER, blob.size, channelProp);
  2454. }
  2455. }
  2456. };
  2457.  
  2458. /**
  2459. * Function that handles the "WRQ" data transfer protocol.
  2460. * @method _WRQProtocolHandler
  2461. * @private
  2462. * @for Skylink
  2463. * @since 0.5.2
  2464. */
  2465. Skylink.prototype._WRQProtocolHandler = function(peerId, data, channelProp) {
  2466. var self = this;
  2467. var transferId = channelProp === 'main' ? data.transferId || null : channelProp;
  2468. var senderPeerId = data.sender || peerId;
  2469.  
  2470. if (['fastBinaryStart', 'fastBinaryStop'].indexOf(data.dataType) > -1) {
  2471. if (data.dataType === 'fastBinaryStart') {
  2472. if (!transferId) {
  2473. transferId = 'stream_' + peerId + '_' + (new Date()).getTime();
  2474. }
  2475. self._dataStreams[transferId] = {
  2476. chunkSize: 0,
  2477. chunkType: data.chunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
  2478. sessionChunkType: data.chunkType,
  2479. isPrivate: !!data.isPrivate,
  2480. isStringStream: data.chunkType === 'string',
  2481. senderPeerId: senderPeerId,
  2482. isUpload: false
  2483. };
  2484. self._dataChannels[peerId][channelProp].streamId = transferId;
  2485. var hasStarted = false;
  2486. self.once('dataChannelState', function () {}, function (state, evtPeerId, channelName, channelType, error) {
  2487. if (!self._dataStreams[transferId]) {
  2488. return true;
  2489. }
  2490.  
  2491. if (!(evtPeerId === peerId && (channelProp === 'main' ? channelType === self.DATA_CHANNEL_TYPE.MESSAGING :
  2492. channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA))) {
  2493. return;
  2494. }
  2495.  
  2496. if ([self.DATA_CHANNEL_STATE.ERROR, self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1) {
  2497. var updatedError = new Error(error && error.message ? error.message :
  2498. 'Failed data transfer as datachannel state is "' + state + '".');
  2499.  
  2500. self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, senderPeerId, {
  2501. chunk: null,
  2502. chunkSize: 0,
  2503. chunkType: self._dataStreams[transferId].chunkType,
  2504. isPrivate: self._dataStreams[transferId].isPrivate,
  2505. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  2506. senderPeerId: senderPeerId
  2507. }, updatedError);
  2508. return true;
  2509. }
  2510. });
  2511.  
  2512. self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVING_STARTED, transferId, senderPeerId, {
  2513. chunk: null,
  2514. chunkSize: 0,
  2515. chunkType: self._dataStreams[transferId].chunkType,
  2516. isPrivate: self._dataStreams[transferId].isPrivate,
  2517. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  2518. senderPeerId: senderPeerId
  2519. }, null);
  2520. self._trigger('incomingDataStreamStarted', transferId, senderPeerId, {
  2521. chunkSize: 0,
  2522. chunkType: self._dataStreams[transferId].chunkType,
  2523. isPrivate: self._dataStreams[transferId].isPrivate,
  2524. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  2525. senderPeerId: senderPeerId
  2526. }, false);
  2527.  
  2528. } else {
  2529. transferId = self._dataChannels[peerId][channelProp].streamId;
  2530. if (self._dataStreams[transferId] && !self._dataStreams[transferId].isUpload) {
  2531. self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVING_STOPPED, transferId, senderPeerId, {
  2532. chunk: null,
  2533. chunkSize: 0,
  2534. chunkType: self._dataStreams[transferId].chunkType,
  2535. isPrivate: self._dataStreams[transferId].isPrivate,
  2536. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  2537. senderPeerId: senderPeerId
  2538. }, null);
  2539. self._trigger('incomingDataStreamStopped', transferId, senderPeerId, {
  2540. chunkSize: 0,
  2541. chunkType: self._dataStreams[transferId].chunkType,
  2542. isPrivate: self._dataStreams[transferId].isPrivate,
  2543. isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
  2544. senderPeerId: senderPeerId
  2545. }, false);
  2546. self._dataChannels[peerId][channelProp].streamId = null;
  2547. if (channelProp !== 'main') {
  2548. self._closeDataChannel(peerId, channelProp);
  2549. }
  2550.  
  2551. delete self._dataStreams[transferId];
  2552. }
  2553. }
  2554. } else {
  2555. if (!transferId) {
  2556. transferId = 'transfer_' + peerId + '_' + (new Date()).getTime();
  2557. }
  2558.  
  2559. self._dataTransfers[transferId] = {
  2560. name: data.name || transferId,
  2561. size: data.size || 0,
  2562. chunkSize: data.chunkSize,
  2563. originalSize: data.originalSize || 0,
  2564. timeout: data.timeout || 60,
  2565. isPrivate: !!data.isPrivate,
  2566. senderPeerId: data.sender || peerId,
  2567. dataType: self.DATA_TRANSFER_SESSION_TYPE.BLOB,
  2568. mimeType: data.mimeType || null,
  2569. chunkType: self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
  2570. direction: self.DATA_TRANSFER_TYPE.DOWNLOAD,
  2571. chunks: [],
  2572. sessions: {},
  2573. sessionType: data.dataType || 'blob',
  2574. sessionChunkType: data.chunkType || 'string'
  2575. };
  2576.  
  2577. if (self._dataTransfers[transferId].sessionType === 'data' &&
  2578. self._dataTransfers[transferId].sessionChunkType === 'string') {
  2579. self._dataTransfers[transferId].dataType = self.DATA_TRANSFER_SESSION_TYPE.DATA_URL;
  2580. self._dataTransfers[transferId].chunkType = self.DATA_TRANSFER_DATA_TYPE.STRING;
  2581. } else if (self._dataTransfers[transferId].sessionType === 'blob' &&
  2582. self._dataTransfers[transferId].sessionChunkType === 'binary') {
  2583. self._dataTransfers[transferId].chunkType = self._binaryChunkType;
  2584. }
  2585.  
  2586. self._dataChannels[peerId][channelProp].transferId = transferId;
  2587. self._dataTransfers[transferId].sessions[peerId] = {
  2588. timer: null,
  2589. ackN: 0,
  2590. receivedSize: 0
  2591. };
  2592.  
  2593. self._trigger('incomingDataRequest', transferId, senderPeerId,
  2594. self._getTransferInfo(transferId, peerId, false, false, false), false);
  2595.  
  2596. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_REQUEST, transferId, senderPeerId,
  2597. self._getTransferInfo(transferId, peerId, true, false, false), null);
  2598. }
  2599. };
  2600.  
  2601. /**
  2602. * Function that handles the "ACK" data transfer protocol.
  2603. * @method _ACKProtocolHandler
  2604. * @private
  2605. * @for Skylink
  2606. * @since 0.5.2
  2607. */
  2608. Skylink.prototype._ACKProtocolHandler = function(peerId, data, channelProp) {
  2609. var self = this;
  2610.  
  2611. var transferId = channelProp;
  2612. var senderPeerId = data.sender || peerId;
  2613.  
  2614. if (channelProp === 'main') {
  2615. transferId = self._dataChannels[peerId].main.transferId;
  2616. }
  2617.  
  2618. self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
  2619.  
  2620. /**
  2621. * Emit event for Peers function.
  2622. */
  2623. var emitEventFn = function (cb) {
  2624. if (peerId === 'MCU') {
  2625. if (!self._dataTransfers[transferId].peers[channelProp]) {
  2626. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of ACK event as ' +
  2627. 'Peers list does not exists']);
  2628. return;
  2629. }
  2630. for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
  2631. if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
  2632. self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
  2633. cb(evtPeerId);
  2634. }
  2635. }
  2636. } else {
  2637. cb(senderPeerId);
  2638. }
  2639. };
  2640.  
  2641. if (data.ackN > -1) {
  2642. if (data.ackN === 0) {
  2643. emitEventFn(function (evtPeerId) {
  2644. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_STARTED, transferId, evtPeerId,
  2645. self._getTransferInfo(transferId, peerId, true, false, 0), null);
  2646. });
  2647. } else if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1 ?
  2648. data.ackN === self._dataTransfers[transferId].enforceBSInfo.chunks.length :
  2649. data.ackN === self._dataTransfers[transferId].chunks.length) {
  2650. self._dataTransfers[transferId].sessions[peerId].ackN = data.ackN;
  2651.  
  2652. emitEventFn(function (evtPeerId) {
  2653. self._trigger('incomingData', self._getTransferData(transferId), transferId, evtPeerId,
  2654. self._getTransferInfo(transferId, peerId, false, false, false), true);
  2655.  
  2656. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED, transferId, evtPeerId,
  2657. self._getTransferInfo(transferId, peerId, true, false, 100), null);
  2658. });
  2659.  
  2660. if (self._dataChannels[peerId][channelProp]) {
  2661. self._dataChannels[peerId][channelProp].transferId = null;
  2662.  
  2663. if (channelProp !== 'main') {
  2664. self._closeDataChannel(peerId, channelProp);
  2665. }
  2666. }
  2667. return;
  2668. }
  2669.  
  2670. var uploadFn = function (chunk) {
  2671. self._sendMessageToDataChannel(peerId, chunk, channelProp, true);
  2672.  
  2673. if (data.ackN < self._dataTransfers[transferId].chunks.length) {
  2674. emitEventFn(function (evtPeerId) {
  2675. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOADING, transferId, evtPeerId,
  2676. self._getTransferInfo(transferId, peerId, true, false, false), null);
  2677. });
  2678. }
  2679.  
  2680. self._handleDataTransferTimeoutForPeer(transferId, peerId, true);
  2681. };
  2682.  
  2683. self._dataTransfers[transferId].sessions[peerId].ackN = data.ackN;
  2684.  
  2685. if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1) {
  2686. self._blobToBase64(self._dataTransfers[transferId].enforceBSInfo.chunks[data.ackN], uploadFn);
  2687. } else if (self._dataTransfers[transferId].chunkType === self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
  2688. self._blobToBase64(self._dataTransfers[transferId].chunks[data.ackN], uploadFn);
  2689. } else if (self._dataTransfers[transferId].chunkType === self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
  2690. self._blobToArrayBuffer(self._dataTransfers[transferId].chunks[data.ackN], uploadFn);
  2691. } else {
  2692. uploadFn(self._dataTransfers[transferId].chunks[data.ackN]);
  2693. }
  2694. } else {
  2695. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.REJECTED, transferId, senderPeerId,
  2696. self._getTransferInfo(transferId, peerId, true, false, false), {
  2697. message: new Error('Data transfer terminated as Peer has rejected data transfer request'),
  2698. transferType: self.DATA_TRANSFER_TYPE.UPLOAD
  2699. });
  2700. }
  2701. };
  2702.  
  2703. /**
  2704. * Function that handles the "MESSAGE" data transfer protocol.
  2705. * @method _MESSAGEProtocolHandler
  2706. * @private
  2707. * @for Skylink
  2708. * @since 0.5.2
  2709. */
  2710. Skylink.prototype._MESSAGEProtocolHandler = function(peerId, data, channelProp) {
  2711. var senderPeerId = data.sender || peerId;
  2712.  
  2713. log.log([senderPeerId, 'RTCDataChannel', channelProp, 'Received P2P message from peer:'], data);
  2714.  
  2715. this._trigger('incomingMessage', {
  2716. content: data.data,
  2717. isPrivate: data.isPrivate,
  2718. isDataChannel: true,
  2719. targetPeerId: this._user.sid,
  2720. senderPeerId: senderPeerId
  2721. }, senderPeerId, this.getPeerInfo(senderPeerId), false);
  2722. };
  2723.  
  2724. /**
  2725. * Function that handles the "ERROR" data transfer protocol.
  2726. * @method _ERRORProtocolHandler
  2727. * @private
  2728. * @for Skylink
  2729. * @since 0.5.2
  2730. */
  2731. Skylink.prototype._ERRORProtocolHandler = function(peerId, data, channelProp) {
  2732. var self = this;
  2733.  
  2734. var transferId = channelProp;
  2735. var senderPeerId = data.sender || peerId;
  2736.  
  2737. if (channelProp === 'main') {
  2738. transferId = self._dataChannels[peerId].main.transferId;
  2739. }
  2740.  
  2741. self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
  2742.  
  2743. /**
  2744. * Emit event for Peers function.
  2745. */
  2746. var emitEventFn = function (cb) {
  2747. if (peerId === 'MCU') {
  2748. if (!self._dataTransfers[transferId].peers[channelProp]) {
  2749. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of ERROR event as ' +
  2750. 'Peers list does not exists']);
  2751. return;
  2752. }
  2753. for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
  2754. if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
  2755. self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
  2756. cb(evtPeerId);
  2757. }
  2758. }
  2759. } else {
  2760. cb(senderPeerId);
  2761. }
  2762. };
  2763.  
  2764. log.error([peerId, 'RTCDataChannel', channelProp, 'Received an error from peer ->'], data);
  2765.  
  2766. emitEventFn(function (evtPeerId) {
  2767. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, evtPeerId,
  2768. self._getTransferInfo(transferId, peerID, true, false, false), {
  2769. message: new Error(data.content),
  2770. transferType: self._dataTransfers[transferId].direction
  2771. });
  2772. });
  2773. };
  2774.  
  2775. /**
  2776. * Function that handles the "CANCEL" data transfer protocol.
  2777. * @method _CANCELProtocolHandler
  2778. * @private
  2779. * @for Skylink
  2780. * @since 0.5.0
  2781. */
  2782. Skylink.prototype._CANCELProtocolHandler = function(peerId, data, channelProp) {
  2783. var self = this;
  2784. var transferId = channelProp;
  2785.  
  2786. if (channelProp === 'main') {
  2787. transferId = self._dataChannels[peerId].main.transferId;
  2788. }
  2789.  
  2790. self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
  2791.  
  2792. /**
  2793. * Emit event for Peers function.
  2794. */
  2795. var emitEventFn = function (cb) {
  2796. if (peerId === 'MCU') {
  2797. if (!self._dataTransfers[transferId].peers[channelProp]) {
  2798. log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of CANCEL event as ' +
  2799. 'Peers list does not exists']);
  2800. return;
  2801. }
  2802. for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
  2803. if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
  2804. self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
  2805. cb(evtPeerId);
  2806. }
  2807. }
  2808. } else {
  2809. cb(peerId);
  2810. }
  2811. };
  2812.  
  2813. log.error([peerId, 'RTCDataChannel', channelProp, 'Received data transfer termination from peer ->'], data);
  2814.  
  2815. emitEventFn(function (evtPeerId) {
  2816. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.CANCEL, transferId, evtPeerId,
  2817. self._getTransferInfo(transferId, peerId, true, false, false), {
  2818. message: new Error(data.content || 'Peer has terminated data transfer.'),
  2819. transferType: self._dataTransfers[transferId].direction
  2820. });
  2821. });
  2822. };
  2823.  
  2824. /**
  2825. * Function that handles the data transfer chunk received.
  2826. * @method _DATAProtocolHandler
  2827. * @private
  2828. * @for Skylink
  2829. * @since 0.5.5
  2830. */
  2831. Skylink.prototype._DATAProtocolHandler = function(peerId, chunk, chunkType, chunkSize, channelProp) {
  2832. var self = this;
  2833. var transferId = channelProp;
  2834. var senderPeerId = peerId;
  2835.  
  2836. if (!(self._dataChannels[peerId] && self._dataChannels[peerId][channelProp])) {
  2837. return;
  2838. }
  2839.  
  2840. var streamId = self._dataChannels[peerId][channelProp].streamId;
  2841.  
  2842. if (streamId && self._dataStreams[streamId] && ((typeof chunk === 'string' &&
  2843. self._dataStreams[streamId].sessionChunkType === 'string') || (chunk instanceof Blob &&
  2844. self._dataStreams[streamId].sessionChunkType === 'binary'))) {
  2845. senderPeerId = self._dataStreams[streamId].senderPeerId || peerId;
  2846. self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVED, streamId, senderPeerId, {
  2847. chunk: chunk,
  2848. chunkSize: chunkSize,
  2849. chunkType: chunkType,
  2850. isPrivate: self._dataStreams[streamId].sessionChunkType.isPrivate,
  2851. isStringStream: self._dataStreams[streamId].sessionChunkType === 'string',
  2852. senderPeerId: senderPeerId
  2853. }, null);
  2854. self._trigger('incomingDataStream', chunk, transferId, senderPeerId, {
  2855. chunkSize: chunkSize,
  2856. chunkType: chunkType,
  2857. isPrivate: self._dataStreams[streamId].sessionChunkType.isPrivate,
  2858. isStringStream: self._dataStreams[streamId].sessionChunkType === 'string',
  2859. senderPeerId: senderPeerId
  2860. }, false);
  2861. return;
  2862. }
  2863.  
  2864. if (channelProp === 'main') {
  2865. transferId = self._dataChannels[peerId].main.transferId;
  2866. }
  2867.  
  2868. if (self._dataTransfers[transferId].senderPeerId) {
  2869. senderPeerId = self._dataTransfers[transferId].senderPeerId;
  2870. }
  2871.  
  2872. self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
  2873.  
  2874. self._dataTransfers[transferId].chunkType = chunkType;
  2875. self._dataTransfers[transferId].sessions[peerId].receivedSize += chunkSize;
  2876. self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN] = chunk;
  2877.  
  2878. if (self._dataTransfers[transferId].sessions[peerId].receivedSize >= self._dataTransfers[transferId].size) {
  2879. log.log([peerId, 'RTCDataChannel', channelProp, 'Data transfer has been completed. Computed size ->'],
  2880. self._dataTransfers[transferId].sessions[peerId].receivedSize);
  2881.  
  2882. // Send last ACK to Peer to indicate completion of data transfers
  2883. self._sendMessageToDataChannel(peerId, {
  2884. type: self._DC_PROTOCOL_TYPE.ACK,
  2885. sender: self._user.sid,
  2886. ackN: self._dataTransfers[transferId].sessions[peerId].ackN + 1
  2887. }, channelProp);
  2888.  
  2889. self._trigger('incomingData', self._getTransferData(transferId), transferId, senderPeerId,
  2890. self._getTransferInfo(transferId, peerId, false, false, false), null);
  2891.  
  2892. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED, transferId, senderPeerId,
  2893. self._getTransferInfo(transferId, peerId, true, false, false), null);
  2894. return;
  2895. }
  2896.  
  2897. self._dataTransfers[transferId].sessions[peerId].ackN += 1;
  2898.  
  2899. self._sendMessageToDataChannel(peerId, {
  2900. type: self._DC_PROTOCOL_TYPE.ACK,
  2901. sender: self._user.sid,
  2902. ackN: self._dataTransfers[transferId].sessions[peerId].ackN
  2903. }, channelProp);
  2904.  
  2905. self._handleDataTransferTimeoutForPeer(transferId, peerId, true);
  2906.  
  2907. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOADING, transferId, senderPeerId,
  2908. self._getTransferInfo(transferId, peerId, true, false, false), null);
  2909. };