File: source/data-transfer.js

  1. /**
  2. * <blockquote class="info">
  3. * Note that this is used only for SDK developer purposes.<br>
  4. * Current version: <code>0.1.0</code>
  5. * </blockquote>
  6. * The value of the current version of the data transfer protocol.
  7. * @attribute DT_PROTOCOL_VERSION
  8. * @type String
  9. * @readOnly
  10. * @for Skylink
  11. * @since 0.5.10
  12. */
  13. Skylink.prototype.DT_PROTOCOL_VERSION = '0.1.0';
  14.  
  15. /**
  16. * The list of data transfers directions.
  17. * @attribute DATA_TRANSFER_TYPE
  18. * @param {String} UPLOAD <small>Value <code>"upload"</code></small>
  19. * The value of the data transfer direction when User is uploading data to Peer.
  20. * @param {String} DOWNLOAD <small>Value <code>"download"</code></small>
  21. * The value of the data transfer direction when User is downloading data from Peer.
  22. * @type JSON
  23. * @readOnly
  24. * @for Skylink
  25. * @since 0.1.0
  26. */
  27. Skylink.prototype.DATA_TRANSFER_TYPE = {
  28. UPLOAD: 'upload',
  29. DOWNLOAD: 'download'
  30. };
  31.  
  32. /**
  33. * The list of data transfers session types.
  34. * @attribute DATA_TRANSFER_SESSION_TYPE
  35. * @param {String} BLOB <small>Value <code>"blob"</code></small>
  36. * The value of the session type for
  37. * <a href="#method_sendURLData"><code>sendURLData()</code> method</a> data transfer.
  38. * @param {String} DATA_URL <small>Value <code>"dataURL"</code></small>
  39. * The value of the session type for
  40. * <a href="#method_sendBlobData"><code>method_sendBlobData()</code> method</a> data transfer.
  41. * @type JSON
  42. * @readOnly
  43. * @for Skylink
  44. * @since 0.1.0
  45. */
  46. Skylink.prototype.DATA_TRANSFER_SESSION_TYPE = {
  47. BLOB: 'blob',
  48. DATA_URL: 'dataURL'
  49. };
  50.  
  51. /**
  52. * The list of data transfer states.
  53. * @attribute DATA_TRANSFER_STATE
  54. * @param {String} UPLOAD_REQUEST <small>Value <code>"request"</code></small>
  55. * The value of the state when receiving an upload data transfer request from Peer to User.
  56. * <small>At this stage, the upload data transfer request from Peer may be accepted or rejected with the
  57. * <a href="#method_acceptDataTransfer"><code>acceptDataTransfer()</code> method</a>.</small>
  58. * @param {String} UPLOAD_STARTED <small>Value <code>"uploadStarted"</code></small>
  59. * The value of the state when the data transfer request has been accepted
  60. * and data transfer will start uploading data to Peer.
  61. * <small>At this stage, the data transfer may be terminated with the
  62. * <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>.</small>
  63. * @param {String} DOWNLOAD_STARTED <small>Value <code>"downloadStarted"</code></small>
  64. * The value of the state when the data transfer request has been accepted
  65. * and data transfer will start downloading data from Peer.
  66. * <small>At this stage, the data transfer may be terminated with the
  67. * <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>.</small>
  68. * @param {String} REJECTED <small>Value <code>"rejected"</code></small>
  69. * The value of the state when upload data transfer request to Peer has been rejected and terminated.
  70. * @param {String} UPLOADING <small>Value <code>"uploading"</code></small>
  71. * The value of the state when data transfer is uploading data to Peer.
  72. * @param {String} DOWNLOADING <small>Value <code>"downloading"</code></small>
  73. * The value of the state when data transfer is downloading data from Peer.
  74. * @param {String} UPLOAD_COMPLETED <small>Value <code>"uploadCompleted"</code></small>
  75. * The value of the state when data transfer has uploaded successfully to Peer.
  76. * @param {String} DOWNLOAD_COMPLETED <small>Value <code>"downloadCompleted"</code></small>
  77. * The value of the state when data transfer has downloaded successfully from Peer.
  78. * @param {String} CANCEL <small>Value <code>"cancel"</code></small>
  79. * The value of the state when data transfer has been terminated from / to Peer.
  80. * @param {String} ERROR <small>Value <code>"error"</code></small>
  81. * The value of the state when data transfer has errors and has been terminated from / to Peer.
  82. * @type JSON
  83. * @readOnly
  84. * @for Skylink
  85. * @since 0.4.0
  86. */
  87. Skylink.prototype.DATA_TRANSFER_STATE = {
  88. UPLOAD_REQUEST: 'request',
  89. UPLOAD_STARTED: 'uploadStarted',
  90. DOWNLOAD_STARTED: 'downloadStarted',
  91. REJECTED: 'rejected',
  92. CANCEL: 'cancel',
  93. ERROR: 'error',
  94. UPLOADING: 'uploading',
  95. DOWNLOADING: 'downloading',
  96. UPLOAD_COMPLETED: 'uploadCompleted',
  97. DOWNLOAD_COMPLETED: 'downloadCompleted'
  98. };
  99.  
  100. /**
  101. * Stores the fixed delimiter that concats the Datachannel label and actual transfer ID.
  102. * @attribute _TRANSFER_DELIMITER
  103. * @type String
  104. * @readOnly
  105. * @private
  106. * @for Skylink
  107. * @since 0.5.10
  108. */
  109. Skylink.prototype._TRANSFER_DELIMITER = '_skylink__';
  110.  
  111. /**
  112. * Stores the list of data transfer protocols.
  113. * @attribute _DC_PROTOCOL_TYPE
  114. * @param {String} WRQ The protocol to initiate data transfer.
  115. * @param {String} ACK The protocol to request for data transfer chunk.
  116. * Give <code>-1</code> to reject the request at the beginning and <code>0</code> to accept
  117. * the data transfer request.
  118. * @param {String} CANCEL The protocol to terminate data transfer.
  119. * @param {String} ERROR The protocol when data transfer has errors and has to be terminated.
  120. * @param {String} MESSAGE The protocol that is used to send P2P messages.
  121. * @type JSON
  122. * @readOnly
  123. * @private
  124. * @for Skylink
  125. * @since 0.5.2
  126. */
  127. Skylink.prototype._DC_PROTOCOL_TYPE = {
  128. WRQ: 'WRQ',
  129. ACK: 'ACK',
  130. ERROR: 'ERROR',
  131. CANCEL: 'CANCEL',
  132. MESSAGE: 'MESSAGE'
  133. };
  134.  
  135. /**
  136. * Stores the list of types of SDKs that do not support simultaneous data transfers.
  137. * @attribute _INTEROP_MULTI_TRANSFERS
  138. * @type Array
  139. * @readOnly
  140. * @private
  141. * @for Skylink
  142. * @since 0.6.1
  143. */
  144. Skylink.prototype._INTEROP_MULTI_TRANSFERS = ['Android', 'iOS'];
  145.  
  146. /**
  147. * Stores the list of uploading data transfers chunks to Peers.
  148. * @attribute _uploadDataTransfers
  149. * @param {Array} <#transferId> The uploading data transfer chunks.
  150. * @type JSON
  151. * @private
  152. * @for Skylink
  153. * @since 0.4.1
  154. */
  155. Skylink.prototype._uploadDataTransfers = {};
  156.  
  157. /**
  158. * Stores the list of uploading data transfer sessions to Peers.
  159. * @attribute _uploadDataSessions
  160. * @param {JSON} <#transferId> The uploading data transfer session.
  161. * @type JSON
  162. * @private
  163. * @for Skylink
  164. * @since 0.4.1
  165. */
  166. Skylink.prototype._uploadDataSessions = {};
  167.  
  168. /**
  169. * Stores the list of downloading data transfers chunks to Peers.
  170. * @attribute _downloadDataTransfers
  171. * @param {Array} <#transferId> The downloading data transfer chunks.
  172. * @type JSON
  173. * @private
  174. * @for Skylink
  175. * @since 0.4.1
  176. */
  177. Skylink.prototype._downloadDataTransfers = {};
  178.  
  179. /**
  180. * Stores the list of downloading data transfer sessions to Peers.
  181. * @attribute _downloadDataSessions
  182. * @param {JSON} <#transferId> The downloading data transfer session.
  183. * @type JSON
  184. * @private
  185. * @for Skylink
  186. * @since 0.4.1
  187. */
  188. Skylink.prototype._downloadDataSessions = {};
  189.  
  190. /**
  191. * Stores the list of data transfer "wait-for-response" timeouts.
  192. * @attribute _dataTransfersTimeout
  193. * @param {Object} <#transferId> The data transfer session "wait-for-response" timeout.
  194. * @type JSON
  195. * @private
  196. * @for Skylink
  197. * @since 0.4.1
  198. */
  199. Skylink.prototype._dataTransfersTimeout = {};
  200.  
  201. /**
  202. * <blockquote class="info">
  203. * Note that Android and iOS SDKs do not support simultaneous data transfers.
  204. * </blockquote>
  205. * Function that starts an uploading data transfer from User to Peers.
  206. * @method sendBlobData
  207. * @param {Blob} data The Blob object.
  208. * @param {Number} [timeout=60] The timeout to wait for response from Peer.
  209. * @param {String|Array} [targetPeerId] The target Peer ID to start data transfer with.
  210. * - When provided as an Array, it will start uploading data transfers with all connections
  211. * with all the Peer IDs provided.
  212. * - When not provided, it will start uploading data transfers with all the currently connected Peers in the Room.
  213. * @param {Function} [callback] The callback function fired when request has completed.
  214. * <small>Function parameters signature is <code>function (error, success)</code></small>
  215. * <small>Function request completion is determined by the <a href="#event_dataTransferState">
  216. * <code>dataTransferState</code> event</a> triggering <code>state</code> parameter payload
  217. * as <code>UPLOAD_COMPLETED</code> for all Peers targeted for request success.</small>
  218. * @param {JSON} callback.error The error result in request.
  219. * <small>Defined as <code>null</code> when there are no errors in request</small>
  220. * @param {String} callback.error.transferId <blockquote class="info">
  221. * <b>Deprecation Warning!</b> This property has been deprecated.
  222. * Please use <code>callback.error.transferInfo.transferId</code> instead.
  223. * </blockquote> The data transfer ID.
  224. * <small>Defined only for single targeted Peer data transfer.</small>
  225. * @param {String} callback.error.state <blockquote class="info">
  226. * <b>Deprecation Warning!</b> This property has been deprecated.
  227. * Please use <a href="#event_dataTransferState"><code>dataTransferState</code>
  228. * event</a> instead. </blockquote> The data transfer state that resulted in error.
  229. * <small>Defined only for single targeted Peer data transfer.</small> [Rel: Skylink.DATA_TRANSFER_STATE]
  230. * @param {String} callback.error.peerId <blockquote class="info">
  231. * <b>Deprecation Warning!</b> This property has been deprecated.
  232. * Please use <code>callback.error.listOfPeers</code> instead.
  233. * </blockquote> The targeted Peer ID for data transfer.
  234. * <small>Defined only for single targeted Peer data transfer.</small>
  235. * @param {Boolean} callback.error.isPrivate <blockquote class="info">
  236. * <b>Deprecation Warning!</b> This property has been deprecated.
  237. * Please use <code>callback.error.transferInfo.isPrivate</code> instead.
  238. * </blockquote> The flag if data transfer is targeted or not, basing
  239. * off the <code>targetPeerId</code> parameter being defined.
  240. * <small>Defined only for single targeted Peer data transfer.</small>
  241. * @param {Error|String} callback.error.error <blockquote class="info">
  242. * <b>Deprecation Warning!</b> This property has been deprecated.
  243. * Please use <code>callback.error.transferErrors</code> instead.
  244. * </blockquote> The error received that resulted in error.
  245. * <small>Defined only for single targeted Peer data transfer.</small>
  246. * @param {Array} callback.error.listOfPeers The list Peer IDs targeted for the data transfer.
  247. * @param {JSON} callback.error.transferErrors The list of data transfer errors.
  248. * @param {Error|String} callback.error.transferErrors.#peerId The data transfer error associated
  249. * with the Peer ID defined in <code>#peerId</code> property.
  250. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  251. * are no Peer connections to start data transfer with.</small>
  252. * @param {JSON} callback.error.transferInfo The data transfer information.
  253. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  254. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>.</small>
  255. * @param {JSON} callback.success The success result in request.
  256. * <small>Defined as <code>null</code> when there are errors in request</small>
  257. * @param {String} callback.success.transferId <blockquote class="info">
  258. * <b>Deprecation Warning!</b> This property has been deprecated.
  259. * Please use <code>callback.success.transferInfo.transferId</code> instead.
  260. * </blockquote> The data transfer ID.
  261. * @param {String} callback.success.state <blockquote class="info">
  262. * <b>Deprecation Warning!</b> This property has been deprecated.
  263. * Please use <a href="#event_dataTransferState"><code>dataTransferState</code>
  264. * event</a> instead. </blockquote> The data transfer state that resulted in error.
  265. * <small>Defined only for single targeted Peer data transfer.</small> [Rel: Skylink.DATA_TRANSFER_STATE]
  266. * @param {String} callback.success.peerId <blockquote class="info">
  267. * <b>Deprecation Warning!</b> This property has been deprecated.
  268. * Please use <code>callback.success.listOfPeers</code> instead.
  269. * </blockquote> The targeted Peer ID for data transfer.
  270. * <small>Defined only for single targeted Peer data transfer.</small>
  271. * @param {Boolean} callback.success.isPrivate <blockquote class="info">
  272. * <b>Deprecation Warning!</b> This property has been deprecated.
  273. * Please use <code>callback.success.transferInfo.isPrivate</code> instead.
  274. * </blockquote> The flag if data transfer is targeted or not, basing
  275. * off the <code>targetPeerId</code> parameter being defined.
  276. * <small>Defined only for single targeted Peer data transfer.</small>
  277. * @param {Array} callback.success.listOfPeers The list Peer IDs targeted for the data transfer.
  278. * @param {JSON} callback.success.transferInfo The data transfer information.
  279. * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
  280. * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>.</small>
  281. * @trigger <ol class="desc-seq">
  282. * <li>Checks if should open a new Datachannel <ol>
  283. * <li>If Peer connection has closed: <small>This can be checked with <a href="#event_peerConnectionState">
  284. * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>CLOSED</code>
  285. * for Peer.</small> <ol><li><b>ABORT</b> step and return error.</li></ol></li>
  286. * <li>If Peer supports simultaneous data transfer, open new Datachannel: <ol>
  287. * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter
  288. * payload <code>state</code> as <code>CONNECTING</code> and <code>channelType</code> as <code>DATA</code>.</li>
  289. * <li>If Datachannel has opened successfully: <ol>
  290. * <li> <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter payload
  291. * <code>state</code> as <code>OPEN</code> and <code>channelType</code> as <code>DATA</code>.</li></ol></li></ol></li>
  292. * <li>Else: <ol><li>If Peer connection Datachannel has not been opened <small>This can be checked with
  293. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
  294. * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
  295. * <code>MESSAGING</code> for Peer.</small> <ol>
  296. * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
  297. * <li>Starts the data transfer to Peer <ol>
  298. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  299. * triggers parameter payload <code>state</code> as <code>UPLOAD_REQUEST</code>.</li>
  300. * <li><a href="#event_incomingDataRequest"><code>incomingDataRequest</code> event</a> triggers.</li>
  301. * <li>Peer invokes <a href="#method_acceptDataTransfer"><code>acceptDataTransfer()</code> method</a>. <ol>
  302. * <li>If parameter <code>accept</code> value is <code>true</code>: <ol>
  303. * <li>User starts upload data transfer to Peer <ol>
  304. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  305. * triggers parameter payload <code>state</code> as <code>UPLOAD_STARTED</code>.</li>
  306. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  307. * triggers parameter payload <code>state</code> as <code>DOWNLOAD_STARTED</code>.</li></ol></li>
  308. * <li>If Peer / User invokes <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>: <ol>
  309. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  310. * <code>state</code> as <code>CANCEL</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  311. * <li>If data transfer has errors: <ol>
  312. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  313. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  314. * <li>If Datachannel has closed abruptly during data transfer:
  315. * <small>This can be checked with <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  316. * triggering parameter payload <code>state</code> as <code>CLOSED</code> and <code>channelType</code>
  317. * as <code>DATA</code> for Peer that supports simultaneous data transfer or <code>MESSAGING</code>
  318. * for Peer that do not support it.</small> <ol>
  319. * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
  320. * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
  321. * <li>If data transfer is still progressing: <ol>
  322. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  323. * triggers parameter payload <code>state</code> as <code>UPLOADING</code>.</li>
  324. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  325. * triggers parameter payload <code>state</code> as <code>DOWNLOADING</code>.</li></ol></li>
  326. * <li>If data transfer has completed <ol>
  327. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  328. * triggers parameter payload <code>state</code> as <code>UPLOAD_COMPLETED</code>.</li>
  329. * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  330. * triggers parameter payload <code>state</code> as <code>DOWNLOAD_COMPLETED</code>.</li>
  331. * <li><a href="#event_incomingData"><code>incomingData</code> event</a> triggers.</li></ol></li></ol></li>
  332. * <li>If parameter <code>accept</code> value is <code>false</code>: <ol>
  333. * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
  334. * triggers parameter payload <code>state</code> as <code>REJECTED</code>.</li>
  335. * <li><b>ABORT</b> step and return error.</li></ol></li></ol>
  336. * @example
  337. * &lt;body&gt;
  338. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(0)"&gt; 1s timeout (Default)
  339. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(120)"&gt; 2s timeout
  340. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(300)"&gt; 5s timeout
  341. * &lt;hr&gt;
  342. * &lt;input type="file" onchange="uploadFile(this.Files[0], this.getAttribute('data'))" data="peerId"&gt;
  343. * &lt;input type="file" onchange="uploadFileGroup(this.Files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB"&gt;
  344. * &lt;input type="file" onchange="uploadFileAll(this.Files[0])" data=""&gt;
  345. * &lt;script&gt;
  346. * var transferTimeout = 0;
  347. *
  348. * function setTransferTimeout (timeout) {
  349. * transferTimeout = timeout;
  350. * }
  351. *
  352. * // Example 1: Upload data to a Peer
  353. * function uploadFile (file, peerId) {
  354. * var cb = function (error, success) {
  355. * if (error) return;
  356. * console.info("File has been transferred to '" + peerId + "' successfully");
  357. * };
  358. * if (transferTimeout > 0) {
  359. * skylinkDemo.sendBlobData(file, peerId, transferTimeout, cb);
  360. * } else {
  361. * skylinkDemo.sendBlobData(file, peerId, cb);
  362. * }
  363. * }
  364. *
  365. * // Example 2: Upload data to a list of Peers
  366. * function uploadFileGroup (file, peerIds) {
  367. * var cb = function (error, success) {
  368. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  369. * var listOfPeersErrors = error ? error.transferErrors : {};
  370. * for (var i = 0; i < listOfPeers.length; i++) {
  371. * if (listOfPeersErrors[listOfPeers[i]]) {
  372. * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
  373. * } else {
  374. * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
  375. * }
  376. * }
  377. * };
  378. * if (transferTimeout > 0) {
  379. * skylinkDemo.sendBlobData(file, peerIds, transferTimeout, cb);
  380. * } else {
  381. * skylinkDemo.sendBlobData(file, peerIds, cb);
  382. * }
  383. * }
  384. *
  385. * // Example 2: Upload data to a list of Peers
  386. * function uploadFileAll (file) {
  387. * var cb = function (error, success) {
  388. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  389. * var listOfPeersErrors = error ? error.transferErrors : {};
  390. * for (var i = 0; i < listOfPeers.length; i++) {
  391. * if (listOfPeersErrors[listOfPeers[i]]) {
  392. * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
  393. * } else {
  394. * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
  395. * }
  396. * }
  397. * };
  398. * if (transferTimeout > 0) {
  399. * skylinkDemo.sendBlobData(file, transferTimeout, cb);
  400. * } else {
  401. * skylinkDemo.sendBlobData(file, cb);
  402. * }
  403. * }
  404. * &lt;/script&gt;
  405. * &lt;/body&gt;
  406. * @for Skylink
  407. * @since 0.5.5
  408. */
  409. Skylink.prototype.sendBlobData = function(data, timeout, targetPeerId, callback) {
  410. var listOfPeers = Object.keys(this._peerConnections);
  411. var isPrivate = false;
  412. var dataInfo = {};
  413. var transferId = this._user.sid + this.DATA_TRANSFER_TYPE.UPLOAD +
  414. (((new Date()).toISOString().replace(/-/g, '').replace(/:/g, ''))).replace('.', '');
  415. // for error case
  416. var errorMsg, errorPayload, i, peerId; // for jshint
  417. var singleError = null;
  418. var transferErrors = {};
  419. var stateError = null;
  420. var singlePeerId = null;
  421.  
  422. //Shift parameters
  423. // timeout
  424. if (typeof timeout === 'function') {
  425. callback = timeout;
  426.  
  427. } else if (typeof timeout === 'string') {
  428. listOfPeers = [timeout];
  429. isPrivate = true;
  430.  
  431. } else if (Array.isArray(timeout)) {
  432. listOfPeers = timeout;
  433. isPrivate = true;
  434. }
  435.  
  436. // targetPeerId
  437. if (typeof targetPeerId === 'function'){
  438. callback = targetPeerId;
  439.  
  440. // data, timeout, target [array], callback
  441. } else if(Array.isArray(targetPeerId)) {
  442. listOfPeers = targetPeerId;
  443. isPrivate = true;
  444.  
  445. // data, timeout, target [string], callback
  446. } else if (typeof targetPeerId === 'string') {
  447. listOfPeers = [targetPeerId];
  448. isPrivate = true;
  449. }
  450.  
  451. //state: String, Deprecated. But for consistency purposes. Null if not a single peer
  452. //error: Object, Deprecated. But for consistency purposes. Null if not a single peer
  453. //transferId: String,
  454. //peerId: String, Deprecated. But for consistency purposes. Null if not a single peer
  455. //listOfPeers: Array, NEW!!
  456. //isPrivate: isPrivate, NEW!!
  457. //transferErrors: JSON, NEW!! - Array of errors
  458. //transferInfo: JSON The same payload as dataTransferState transferInfo payload
  459.  
  460. // check if it's blob data
  461. if (!(typeof data === 'object' && data instanceof Blob)) {
  462. errorMsg = 'Provided data is not a Blob data';
  463.  
  464. if (listOfPeers.length === 0) {
  465. transferErrors.self = errorMsg;
  466.  
  467. } else {
  468. for (i = 0; i < listOfPeers.length; i++) {
  469. peerId = listOfPeers[i];
  470. transferErrors[peerId] = errorMsg;
  471. }
  472.  
  473. // Deprecated but for consistency purposes. Null if not a single peer.
  474. if (listOfPeers.length === 1 && isPrivate) {
  475. stateError = self.DATA_TRANSFER_STATE.ERROR;
  476. singleError = errorMsg;
  477. singlePeerId = listOfPeers[0];
  478. }
  479. }
  480.  
  481. errorPayload = {
  482. state: stateError,
  483. error: singleError,
  484. transferId: transferId,
  485. peerId: singlePeerId,
  486. listOfPeers: listOfPeers,
  487. transferErrors: transferErrors,
  488. transferInfo: dataInfo,
  489. isPrivate: isPrivate
  490. };
  491.  
  492. log.error(errorMsg, errorPayload);
  493.  
  494. if (typeof callback === 'function'){
  495. log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
  496. 'with error -> '],errorPayload);
  497. callback(errorPayload, null);
  498. }
  499. return;
  500. }
  501.  
  502. // populate data
  503. dataInfo.name = data.name || transferId;
  504. dataInfo.size = data.size;
  505. dataInfo.timeout = typeof timeout === 'number' ? timeout : 60;
  506. dataInfo.transferId = transferId;
  507. dataInfo.dataType = 'blob';
  508. dataInfo.isPrivate = isPrivate;
  509.  
  510. // check if datachannel is enabled first or not
  511. if (!this._enableDataChannel) {
  512. errorMsg = 'Unable to send any blob data. Datachannel is disabled';
  513.  
  514. if (listOfPeers.length === 0) {
  515. transferErrors.self = errorMsg;
  516.  
  517. } else {
  518. for (i = 0; i < listOfPeers.length; i++) {
  519. peerId = listOfPeers[i];
  520. transferErrors[peerId] = errorMsg;
  521. }
  522.  
  523. // Deprecated but for consistency purposes. Null if not a single peer.
  524. if (listOfPeers.length === 1 && isPrivate) {
  525. stateError = self.DATA_TRANSFER_STATE.ERROR;
  526. singleError = errorMsg;
  527. singlePeerId = listOfPeers[0];
  528. }
  529. }
  530.  
  531. errorPayload = {
  532. state: stateError,
  533. error: singleError,
  534. transferId: transferId,
  535. peerId: singlePeerId,
  536. listOfPeers: listOfPeers,
  537. transferErrors: transferErrors,
  538. transferInfo: dataInfo,
  539. isPrivate: isPrivate
  540. };
  541.  
  542. log.error(errorMsg, errorPayload);
  543.  
  544. if (typeof callback === 'function'){
  545. log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
  546. 'with error -> '], errorPayload);
  547. callback(errorPayload, null);
  548. }
  549. return;
  550. }
  551.  
  552. this._startDataTransfer(data, dataInfo, listOfPeers, callback);
  553. };
  554.  
  555. /**
  556. * <blockquote class="info">
  557. * <b>Deprecation Warning!</b> This method has been deprecated, please use <a href="#method_acceptDataTransfer">
  558. * <code>acceptDataTransfer()</code> method</a> instead.
  559. * </blockquote>
  560. * Function that accepts or rejects an upload data transfer request from Peer to User.
  561. * <small>Parameter signature follows <a href="#method_acceptDataTransfer">
  562. * <code>acceptDataTransfer()</code> method</a>.</small>
  563. * @method respondBlobRequest
  564. * @example
  565. * // Example 1: Accept Peer upload data transfer request
  566. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  567. * if (!isSelf) {
  568. * skylinkDemo.respondBlobRequest(peerId, transferId, true);
  569. * }
  570. * });
  571. *
  572. * // Example 2: Reject Peer upload data transfer request
  573. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  574. * if (!isSelf) {
  575. * skylinkDemo.respondBlobRequest(peerId, transferId, false);
  576. * }
  577. * });
  578. * @deprecated true
  579. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  580. * <code>sendBlobData()</code> method</a> after <code>acceptDataTransfer()</code> method is invoked.</small>
  581. * @for Skylink
  582. * @since 0.5.0
  583. */
  584. Skylink.prototype.respondBlobRequest =
  585. /**
  586. * Function that accepts or rejects an upload data transfer request from Peer to User.
  587. * @method acceptDataTransfer
  588. * @param {String} peerId The Peer ID.
  589. * @param {String} transferId The data transfer ID.
  590. * @param {Boolean} [accept=false] The flag if User accepts the upload data transfer request from Peer.
  591. * @example
  592. * // Example 1: Accept Peer upload data transfer request
  593. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  594. * if (!isSelf) {
  595. * skylinkDemo.acceptDataTransfer(peerId, transferId, true);
  596. * }
  597. * });
  598. *
  599. * // Example 2: Reject Peer upload data transfer request
  600. * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
  601. * if (!isSelf) {
  602. * skylinkDemo.acceptDataTransfer(peerId, transferId, false);
  603. * }
  604. * });
  605. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  606. * <code>sendBlobData()</code> method</a> after <code>acceptDataTransfer()</code> method is invoked.</small>
  607. * @for Skylink
  608. * @since 0.6.1
  609. */
  610. Skylink.prototype.acceptDataTransfer = function (peerId, transferId, accept) {
  611. if (typeof transferId !== 'string' && typeof peerId !== 'string') {
  612. log.error([peerId, 'RTCDataChannel', null, 'Aborting accept data transfer as ' +
  613. 'transfer ID and peer ID is not provided'], {
  614. accept: accept,
  615. peerId: peerId,
  616. transferId: transferId
  617. });
  618. return;
  619. }
  620.  
  621. if (transferId.indexOf(this._TRANSFER_DELIMITER) === -1) {
  622. log.error([peerId, 'RTCDataChannel', null, 'Aborting accept data transfer as ' +
  623. 'invalid transfer ID is provided'], {
  624. accept: accept,
  625. transferId: transferId
  626. });
  627. return;
  628. }
  629. var channelName = transferId.split(this._TRANSFER_DELIMITER)[0];
  630.  
  631. if (accept) {
  632.  
  633. log.info([peerId, 'RTCDataChannel', channelName, 'User accepted peer\'s request'], {
  634. accept: accept,
  635. transferId: transferId
  636. });
  637.  
  638. if (!this._peerInformations[peerId] && !this._peerInformations[peerId].agent) {
  639. log.error([peerId, 'RTCDataChannel', channelName, 'Aborting accept data transfer as ' +
  640. 'Peer informations for peer is missing'], {
  641. accept: accept,
  642. transferId: transferId
  643. });
  644. return;
  645. }
  646.  
  647. this._downloadDataTransfers[channelName] = [];
  648.  
  649. var data = this._downloadDataSessions[channelName];
  650. this._sendDataChannelMessage(peerId, {
  651. type: this._DC_PROTOCOL_TYPE.ACK,
  652. sender: this._user.sid,
  653. ackN: 0,
  654. agent: window.webrtcDetectedBrowser
  655. }, channelName);
  656. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
  657. data.transferId, peerId, {
  658. name: data.name,
  659. size: data.size,
  660. data: null,
  661. dataType: data.dataType,
  662. percentage: 0,
  663. senderPeerId: peerId,
  664. timeout: data.timeout,
  665. isPrivate: data.isPrivate
  666. });
  667. } else {
  668. log.info([peerId, 'RTCDataChannel', channelName, 'User rejected peer\'s request'], {
  669. accept: accept,
  670. transferId: transferId
  671. });
  672. this._sendDataChannelMessage(peerId, {
  673. type: this._DC_PROTOCOL_TYPE.ACK,
  674. sender: this._user.sid,
  675. ackN: -1
  676. }, channelName);
  677. delete this._downloadDataSessions[channelName];
  678. delete this._downloadDataTransfers[channelName];
  679. }
  680. };
  681.  
  682. /**
  683. * <blockquote class="info">
  684. * <b>Deprecation Warning!</b> This method has been deprecated, please use <a href="#method_cancelDataTransfer">
  685. * <code>method_cancelDataTransfer()</code> method</a> instead.
  686. * </blockquote>
  687. * Function that terminates a currently uploading / downloading data transfer from / to Peer.
  688. * <small>Parameter signature follows <a href="#method_cancelDataTransfer">
  689. * <code>cancelDataTransfer()</code> method</a>.</small>
  690. * @method cancelBlobTransfer
  691. * @trigger <small>Event sequence follows <a href="#method_acceptDataTransfer">
  692. * @example
  693. * // Example 1: Cancel Peer data transfer
  694. * var transferSessions = {};
  695. *
  696. * skylinkDemo.on("dataTransferState", function (state, transferId, peerId) {
  697. * if ([skylinkDemo.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
  698. * skylinkDemo.DATA_TRANSFER_STATE.UPLOAD_STARTED].indexOf(state) > -1) {
  699. * if (!Array.isArray(transferSessions[transferId])) {
  700. * transferSessions[transferId] = [];
  701. * }
  702. * transferSessions[transferId].push(peerId);
  703. * } else {
  704. * transferSessions[transferId].splice(transferSessions[transferId].indexOf(peerId), 1);
  705. * }
  706. * });
  707. *
  708. * function cancelTransfer (peerId, transferId) {
  709. * skylinkDemo.cancelBlobTransfer(peerId, transferId);
  710. * }
  711. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  712. * <code>sendBlobData()</code> method</a> after <code>cancelDataTransfer()</code> method is invoked.</small>
  713. * @for Skylink
  714. * @deprecated true
  715. * @for Skylink
  716. * @since 0.5.7
  717. */
  718. Skylink.prototype.cancelBlobTransfer =
  719. /**
  720. * Function that terminates a currently uploading / downloading data transfer from / to Peer.
  721. * @method cancelDataTransfer
  722. * @param {String} peerId The Peer ID.
  723. * @param {String} transferId The data transfer ID.
  724. * @example
  725. * // Example 1: Cancel Peer data transfer
  726. * var transferSessions = {};
  727. *
  728. * skylinkDemo.on("dataTransferState", function (state, transferId, peerId) {
  729. * if ([skylinkDemo.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
  730. * skylinkDemo.DATA_TRANSFER_STATE.UPLOAD_STARTED].indexOf(state) > -1) {
  731. * if (!Array.isArray(transferSessions[transferId])) {
  732. * transferSessions[transferId] = [];
  733. * }
  734. * transferSessions[transferId].push(peerId);
  735. * } else {
  736. * transferSessions[transferId].splice(transferSessions[transferId].indexOf(peerId), 1);
  737. * }
  738. * });
  739. *
  740. * function cancelTransfer (peerId, transferId) {
  741. * skylinkDemo.cancelDataTransfer(peerId, transferId);
  742. * }
  743. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  744. * <code>sendBlobData()</code> method</a> after <code>cancelDataTransfer()</code> method is invoked.</small>
  745. * @for Skylink
  746. * @since 0.6.1
  747. */
  748. Skylink.prototype.cancelDataTransfer = function (peerId, transferId) {
  749. var data;
  750.  
  751. // targetPeerId + '-' + transferId
  752. var channelName = peerId + '-' + transferId;
  753.  
  754. if (transferId.indexOf(this._TRANSFER_DELIMITER) > 0) {
  755. channelName = transferId.split(this._TRANSFER_DELIMITER)[0];
  756. } else {
  757.  
  758. var peerAgent = (this._peerInformations[peerId] || {}).agent;
  759.  
  760. if (!peerAgent && !peerAgent.name) {
  761. log.error([peerId, 'RTCDataChannel', null, 'Cancel transfer to peer ' +
  762. 'failed as peer agent information for peer does not exists'], transferId);
  763. return;
  764. }
  765.  
  766. if (self._INTEROP_MULTI_TRANSFERS.indexOf(peerAgent.name) > -1) {
  767. channelName = peerId;
  768. }
  769. }
  770.  
  771. if (this._uploadDataSessions[channelName]) {
  772. data = this._uploadDataSessions[channelName];
  773.  
  774. delete this._uploadDataSessions[channelName];
  775. delete this._uploadDataTransfers[channelName];
  776.  
  777. // send message
  778. this._sendDataChannelMessage(peerId, {
  779. type: this._DC_PROTOCOL_TYPE.CANCEL,
  780. sender: this._user.sid,
  781. name: data.name,
  782. content: 'Peer cancelled upload transfer'
  783. }, channelName);
  784.  
  785. log.debug([peerId, 'RTCDataChannel', channelName,
  786. 'Cancelling upload data transfers'], transferId);
  787.  
  788. } else if (this._downloadDataSessions[channelName]) {
  789. data = this._downloadDataSessions[channelName];
  790.  
  791. delete this._downloadDataSessions[channelName];
  792. delete this._downloadDataTransfers[channelName];
  793.  
  794. // send message
  795. this._sendDataChannelMessage(peerId, {
  796. type: this._DC_PROTOCOL_TYPE.CANCEL,
  797. sender: this._user.sid,
  798. name: data.name,
  799. content: 'Peer cancelled download transfer'
  800. }, channelName);
  801.  
  802. log.debug([peerId, 'RTCDataChannel', channelName,
  803. 'Cancelling download data transfers'], transferId);
  804.  
  805. } else {
  806. log.error([peerId, 'RTCDataChannel', null, 'Cancel transfer to peer ' +
  807. 'failed as transfer session with peer does not exists'], transferId);
  808. return;
  809. }
  810.  
  811. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.CANCEL,
  812. data.transferId, peerId, {
  813. name: data.name,
  814. size: data.size,
  815. percentage: data.percentage,
  816. data: null,
  817. dataType: data.dataType,
  818. senderPeerId: data.senderPeerId,
  819. timeout: data.timeout,
  820. isPrivate: data.isPrivate
  821. });
  822. };
  823.  
  824. /**
  825. * Function that sends a message to Peers via the Datachannel connection.
  826. * <small>Consider using <a href="#method_sendURLData"><code>sendURLData()</code> method</a> if you are
  827. * sending large strings to Peers.</small>
  828. * @method sendP2PMessage
  829. * @param {String|JSON} message The message.
  830. * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
  831. * - When provided as an Array, it will send the message to only Peers which IDs are in the list.
  832. * - When not provided, it will broadcast the message to all connected Peers in the Room.
  833. * @trigger <ol class="desc-seq">
  834. * <li>Sends P2P message to all targeted Peers. <ol>
  835. * <li>If Peer connection Datachannel has not been opened: <small>This can be checked with
  836. * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
  837. * triggering parameter payload <code>state</code> as <code>OPEN</code> and
  838. * <code>channelType</code> as <code>MESSAGING</code> for Peer.</small> <ol>
  839. * <li><b>ABORT</b> step and return error.</li></ol></li>
  840. * <li><a href="#event_incomingMessage"><code>incomingMessage</code> event</a> triggers
  841. * parameter payload <code>message.isDataChannel</code> value as <code>true</code> and
  842. * <code>isSelf</code> value as <code>true</code>.</li></ol></li></ol>
  843. * @example
  844. * // Example 1: Broadcasting to all Peers
  845. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  846. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  847. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  848. * skylinkDemo.sendP2PMessage("Hi all!");
  849. * }
  850. * });
  851. *
  852. * // Example 2: Sending to specific Peers
  853. * var peersInExclusiveParty = [];
  854. *
  855. * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
  856. * if (isSelf) return;
  857. * if (peerInfo.userData.exclusive) {
  858. * peersInExclusiveParty[peerId] = false;
  859. * }
  860. * });
  861. *
  862. * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
  863. * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
  864. * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
  865. * peersInExclusiveParty[peerId] = true;
  866. * }
  867. * });
  868. *
  869. * function updateExclusivePartyStatus (message) {
  870. * var readyToSend = [];
  871. * for (var p in peersInExclusiveParty) {
  872. * if (peersInExclusiveParty.hasOwnProperty(p)) {
  873. * readyToSend.push(p);
  874. * }
  875. * }
  876. * skylinkDemo.sendP2PMessage(message, readyToSend);
  877. * }
  878. * @for Skylink
  879. * @since 0.5.5
  880. */
  881. Skylink.prototype.sendP2PMessage = function(message, targetPeerId) {
  882. var self = this;
  883.  
  884. // check if datachannel is enabled first or not
  885. if (!self._enableDataChannel) {
  886. log.warn('Unable to send any P2P message. Datachannel is disabled');
  887. return;
  888. }
  889.  
  890. var listOfPeers = Object.keys(self._dataChannels);
  891. var isPrivate = false;
  892.  
  893. //targetPeerId is defined -> private message
  894. if (Array.isArray(targetPeerId)) {
  895. listOfPeers = targetPeerId;
  896. isPrivate = true;
  897.  
  898. } else if (typeof targetPeerId === 'string') {
  899. listOfPeers = [targetPeerId];
  900. isPrivate = true;
  901. }
  902.  
  903. // sending public message to MCU to relay. MCU case only
  904. if (self._hasMCU) {
  905. if (isPrivate) {
  906. log.log(['MCU', null, null, 'Relaying private P2P message to peers'], listOfPeers);
  907. self._sendDataChannelMessage('MCU', {
  908. type: self._DC_PROTOCOL_TYPE.MESSAGE,
  909. isPrivate: isPrivate,
  910. sender: self._user.sid,
  911. target: listOfPeers,
  912. data: message
  913. });
  914. } else {
  915. log.log(['MCU', null, null, 'Relaying P2P message to peers']);
  916.  
  917. self._sendDataChannelMessage('MCU', {
  918. type: self._DC_PROTOCOL_TYPE.MESSAGE,
  919. isPrivate: isPrivate,
  920. sender: self._user.sid,
  921. target: 'MCU',
  922. data: message
  923. });
  924. }
  925. } else {
  926. for (var i = 0; i < listOfPeers.length; i++) {
  927. var peerId = listOfPeers[i];
  928. var useChannel = (self._hasMCU) ? 'MCU' : peerId;
  929.  
  930. // Ignore MCU peer
  931. if (peerId === 'MCU') {
  932. continue;
  933. }
  934.  
  935. log.log([peerId, null, useChannel, 'Sending P2P message to peer']);
  936.  
  937. self._sendDataChannelMessage(useChannel, {
  938. type: self._DC_PROTOCOL_TYPE.MESSAGE,
  939. isPrivate: isPrivate,
  940. sender: self._user.sid,
  941. target: peerId,
  942. data: message
  943. });
  944. }
  945. }
  946.  
  947. self._trigger('incomingMessage', {
  948. content: message,
  949. isPrivate: isPrivate,
  950. targetPeerId: targetPeerId || null,
  951. isDataChannel: true,
  952. senderPeerId: self._user.sid
  953. }, self._user.sid, self.getPeerInfo(), true);
  954. };
  955.  
  956. /**
  957. * <blockquote class="info">
  958. * Currently, the Android and iOS SDKs do not support this type of data transfer.
  959. * </blockquote>
  960. * Function that starts an uploading string data transfer from User to Peers.
  961. * @method sendURLData
  962. * @param {String} data The data string to transfer to Peer.
  963. * <small>Parameter signature follows <a href="#method_sendBlobData">
  964. * <code>sendBlobData()</code> method</a> except <code>data</code> parameter.</small>
  965. * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
  966. * <code>sendBlobData()</code> method</a>.</small>
  967. * @example
  968. * &lt;body&gt;
  969. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(0)"&gt; 1s timeout (Default)
  970. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(120)"&gt; 2s timeout
  971. * &lt;input type="radio" name="timeout" onchange="setTransferTimeout(300)"&gt; 5s timeout
  972. * &lt;hr&gt;
  973. * &lt;input type="file" onchange="showImage(this.Files[0], this.getAttribute('data'))" data="peerId"&gt;
  974. * &lt;input type="file" onchange="showImageGroup(this.Files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB"&gt;
  975. * &lt;input type="file" onchange="showImageAll(this.Files[0])" data=""&gt;
  976. * &lt;image id="target-1" src=""&gt;
  977. * &lt;image id="target-2" src=""&gt;
  978. * &lt;image id="target-3" src=""&gt;
  979. * &lt;script&gt;
  980. * var transferTimeout = 0;
  981. *
  982. * function setTransferTimeout (timeout) {
  983. * transferTimeout = timeout;
  984. * }
  985. *
  986. * function retrieveImageDataURL(file, cb) {
  987. * var fr = new FileReader();
  988. * fr.onload = function () {
  989. * cb(fr.result);
  990. * };
  991. * fr.readAsDataURL(files[0]);
  992. * }
  993. *
  994. * // Example 1: Send image data URL to a Peer
  995. * function showImage (file, peerId) {
  996. * var cb = function (error, success) {
  997. * if (error) return;
  998. * console.info("Image has been transferred to '" + peerId + "' successfully");
  999. * };
  1000. * retrieveImageDataURL(file, function (str) {
  1001. * if (transferTimeout > 0) {
  1002. * skylinkDemo.sendURLData(str, peerId, transferTimeout, cb);
  1003. * } else {
  1004. * skylinkDemo.sendURLData(str, peerId, cb);
  1005. * }
  1006. * document.getElementById("target-1").src = str;
  1007. * });
  1008. * }
  1009. *
  1010. * // Example 2: Send image data URL to a list of Peers
  1011. * function showImageGroup (file, peerIds) {
  1012. * var cb = function (error, success) {
  1013. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  1014. * var listOfPeersErrors = error ? error.transferErrors : {};
  1015. * for (var i = 0; i < listOfPeers.length; i++) {
  1016. * if (listOfPeersErrors[listOfPeers[i]]) {
  1017. * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
  1018. * } else {
  1019. * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
  1020. * }
  1021. * }
  1022. * };
  1023. * retrieveImageDataURL(file, function (str) {
  1024. * if (transferTimeout > 0) {
  1025. * skylinkDemo.sendURLData(str, peerIds, transferTimeout, cb);
  1026. * } else {
  1027. * skylinkDemo.sendURLData(str, peerIds, cb);
  1028. * }
  1029. * document.getElementById("target-2").src = str;
  1030. * });
  1031. * }
  1032. *
  1033. * // Example 2: Send image data URL to a list of Peers
  1034. * function uploadFileAll (file) {
  1035. * var cb = function (error, success) {
  1036. * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
  1037. * var listOfPeersErrors = error ? error.transferErrors : {};
  1038. * for (var i = 0; i < listOfPeers.length; i++) {
  1039. * if (listOfPeersErrors[listOfPeers[i]]) {
  1040. * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
  1041. * } else {
  1042. * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
  1043. * }
  1044. * }
  1045. * };
  1046. * retrieveImageDataURL(file, function (str) {
  1047. * if (transferTimeout > 0) {
  1048. * skylinkDemo.sendURLData(str, transferTimeout, cb);
  1049. * } else {
  1050. * skylinkDemo.sendURLData(str, cb);
  1051. * }
  1052. * document.getElementById("target-3").src = str;
  1053. * });
  1054. * }
  1055. * &lt;/script&gt;
  1056. * &lt;/body&gt;
  1057. * @for Skylink
  1058. * @since 0.6.1
  1059. */
  1060. Skylink.prototype.sendURLData = function(data, timeout, targetPeerId, callback) {
  1061. var listOfPeers = Object.keys(this._peerConnections);
  1062. var isPrivate = false;
  1063. var dataInfo = {};
  1064. var transferId = this._user.sid + this.DATA_TRANSFER_TYPE.UPLOAD +
  1065. (((new Date()).toISOString().replace(/-/g, '').replace(/:/g, ''))).replace('.', '');
  1066. // for error case
  1067. var errorMsg, errorPayload, i, peerId; // for jshint
  1068. var singleError = null;
  1069. var transferErrors = {};
  1070. var stateError = null;
  1071. var singlePeerId = null;
  1072.  
  1073. //Shift parameters
  1074. // timeout
  1075. if (typeof timeout === 'function') {
  1076. callback = timeout;
  1077.  
  1078. } else if (typeof timeout === 'string') {
  1079. listOfPeers = [timeout];
  1080. isPrivate = true;
  1081.  
  1082. } else if (Array.isArray(timeout)) {
  1083. listOfPeers = timeout;
  1084. isPrivate = true;
  1085. }
  1086.  
  1087. // targetPeerId
  1088. if (typeof targetPeerId === 'function'){
  1089. callback = targetPeerId;
  1090.  
  1091. // data, timeout, target [array], callback
  1092. } else if(Array.isArray(targetPeerId)) {
  1093. listOfPeers = targetPeerId;
  1094. isPrivate = true;
  1095.  
  1096. // data, timeout, target [string], callback
  1097. } else if (typeof targetPeerId === 'string') {
  1098. listOfPeers = [targetPeerId];
  1099. isPrivate = true;
  1100. }
  1101.  
  1102. //state: String, Deprecated. But for consistency purposes. Null if not a single peer
  1103. //error: Object, Deprecated. But for consistency purposes. Null if not a single peer
  1104. //transferId: String,
  1105. //peerId: String, Deprecated. But for consistency purposes. Null if not a single peer
  1106. //listOfPeers: Array, NEW!!
  1107. //isPrivate: isPrivate, NEW!!
  1108. //transferErrors: JSON, NEW!! - Array of errors
  1109. //transferInfo: JSON The same payload as dataTransferState transferInfo payload
  1110.  
  1111. // check if it's blob data
  1112. if (typeof data !== 'string') {
  1113. errorMsg = 'Provided data is not a dataURL';
  1114.  
  1115. if (listOfPeers.length === 0) {
  1116. transferErrors.self = errorMsg;
  1117.  
  1118. } else {
  1119. for (i = 0; i < listOfPeers.length; i++) {
  1120. peerId = listOfPeers[i];
  1121. transferErrors[peerId] = errorMsg;
  1122. }
  1123.  
  1124. // Deprecated but for consistency purposes. Null if not a single peer.
  1125. if (listOfPeers.length === 1 && isPrivate) {
  1126. stateError = self.DATA_TRANSFER_STATE.ERROR;
  1127. singleError = errorMsg;
  1128. singlePeerId = listOfPeers[0];
  1129. }
  1130. }
  1131.  
  1132. errorPayload = {
  1133. state: stateError,
  1134. error: singleError,
  1135. transferId: transferId,
  1136. peerId: singlePeerId,
  1137. listOfPeers: listOfPeers,
  1138. transferErrors: transferErrors,
  1139. transferInfo: dataInfo,
  1140. isPrivate: isPrivate
  1141. };
  1142.  
  1143. log.error(errorMsg, errorPayload);
  1144.  
  1145. if (typeof callback === 'function'){
  1146. log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
  1147. 'with error -> '],errorPayload);
  1148. callback(errorPayload, null);
  1149. }
  1150. return;
  1151. }
  1152.  
  1153. // populate data
  1154. dataInfo.name = data.name || transferId;
  1155. dataInfo.size = data.size || data.length;
  1156. dataInfo.timeout = typeof timeout === 'number' ? timeout : 60;
  1157. dataInfo.transferId = transferId;
  1158. dataInfo.dataType = 'dataURL';
  1159. dataInfo.isPrivate = isPrivate;
  1160.  
  1161. // check if datachannel is enabled first or not
  1162. if (!this._enableDataChannel) {
  1163. errorMsg = 'Unable to send any dataURL. Datachannel is disabled';
  1164.  
  1165. if (listOfPeers.length === 0) {
  1166. transferErrors.self = errorMsg;
  1167.  
  1168. } else {
  1169. for (i = 0; i < listOfPeers.length; i++) {
  1170. peerId = listOfPeers[i];
  1171. transferErrors[peerId] = errorMsg;
  1172. }
  1173.  
  1174. // Deprecated but for consistency purposes. Null if not a single peer.
  1175. if (listOfPeers.length === 1 && isPrivate) {
  1176. stateError = self.DATA_TRANSFER_STATE.ERROR;
  1177. singleError = errorMsg;
  1178. singlePeerId = listOfPeers[0];
  1179. }
  1180. }
  1181.  
  1182. errorPayload = {
  1183. state: stateError,
  1184. error: singleError,
  1185. transferId: transferId,
  1186. peerId: singlePeerId,
  1187. listOfPeers: listOfPeers,
  1188. transferErrors: transferErrors,
  1189. transferInfo: dataInfo,
  1190. isPrivate: isPrivate
  1191. };
  1192.  
  1193. log.error(errorMsg, errorPayload);
  1194.  
  1195. if (typeof callback === 'function'){
  1196. log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
  1197. 'with error -> '], errorPayload);
  1198. callback(errorPayload, null);
  1199. }
  1200. return;
  1201. }
  1202.  
  1203. this._startDataTransfer(data, dataInfo, listOfPeers, callback);
  1204. };
  1205.  
  1206. /**
  1207. * Function that sets the data transfer "wait-for-response" timeout.
  1208. * When there is not response after timeout, the data transfer will be terminated.
  1209. * @method _setDataChannelTimeout
  1210. * @private
  1211. * @for Skylink
  1212. * @since 0.5.0
  1213. */
  1214. Skylink.prototype._setDataChannelTimeout = function(peerId, timeout, isSender, channelName) {
  1215. var self = this;
  1216. if (!self._dataTransfersTimeout[channelName]) {
  1217. self._dataTransfersTimeout[channelName] = null;
  1218. }
  1219. var type = (isSender) ? self.DATA_TRANSFER_TYPE.UPLOAD :
  1220. self.DATA_TRANSFER_TYPE.DOWNLOAD;
  1221.  
  1222. self._dataTransfersTimeout[channelName] = setTimeout(function() {
  1223. var name;
  1224. if (self._dataTransfersTimeout[channelName][type]) {
  1225. if (isSender) {
  1226. name = self._uploadDataSessions[channelName].name;
  1227. delete self._uploadDataTransfers[channelName];
  1228. delete self._uploadDataSessions[channelName];
  1229. } else {
  1230. name = self._downloadDataSessions[channelName].name;
  1231. delete self._downloadDataTransfers[channelName];
  1232. delete self._downloadDataSessions[channelName];
  1233. }
  1234.  
  1235. self._sendDataChannelMessage(peerId, {
  1236. type: self._DC_PROTOCOL_TYPE.ERROR,
  1237. sender: self._user.sid,
  1238. name: name,
  1239. content: 'Connection Timeout. Longer than ' + timeout +
  1240. ' seconds. Connection is abolished.',
  1241. isUploadError: isSender
  1242. }, channelName);
  1243. // TODO: Find a way to add channel name so it's more specific
  1244. log.error([peerId, 'RTCDataChannel', channelName, 'Failed transfering data:'],
  1245. 'Transfer ' + ((isSender) ? 'for': 'from') + ' ' + peerId +
  1246. ' failed. Connection timeout');
  1247. self._clearDataChannelTimeout(peerId, isSender, channelName);
  1248. }
  1249. }, 1000 * timeout);
  1250. };
  1251.  
  1252. /**
  1253. * Function that stops and clears the data transfer "wait-for-response" timeout.
  1254. * @method _clearDataChannelTimeout
  1255. * @private
  1256. * @for Skylink
  1257. * @since 0.5.0
  1258. */
  1259. Skylink.prototype._clearDataChannelTimeout = function(peerId, isSender, channelName) {
  1260. if (this._dataTransfersTimeout[channelName]) {
  1261. clearTimeout(this._dataTransfersTimeout[channelName]);
  1262. delete this._dataTransfersTimeout[channelName];
  1263. log.debug([peerId, 'RTCDataChannel', channelName, 'Clear datachannel timeout']);
  1264. } else {
  1265. log.debug([peerId, 'RTCDataChannel', channelName, 'Unable to find timeouts. ' +
  1266. 'Not clearing the datachannel timeouts']);
  1267. }
  1268. };
  1269.  
  1270. /**
  1271. * Function that starts a data transfer to Peer.
  1272. * This will open a new data type of Datachannel connection with Peer if
  1273. * simultaneous data transfers is supported by Peer.
  1274. * @method _sendBlobDataToPeer
  1275. * @private
  1276. * @for Skylink
  1277. * @since 0.5.5
  1278. */
  1279. Skylink.prototype._sendBlobDataToPeer = function(data, dataInfo, targetPeerId) {
  1280. var self = this;
  1281. //If there is MCU then directs all messages to MCU
  1282. var targetChannel = targetPeerId;//(self._hasMCU) ? 'MCU' : targetPeerId;
  1283. var targetPeerList = [];
  1284.  
  1285. var binarySize = parseInt((dataInfo.size * (4 / 3)).toFixed(), 10);
  1286. var binaryChunkSize = 0;
  1287. var chunkSize = 0;
  1288. var i;
  1289. var hasSend = false;
  1290.  
  1291. // move list of peers to targetPeerList
  1292. if (self._hasMCU) {
  1293. if (Array.isArray(targetPeerList)) {
  1294. targetPeerList = targetPeerId;
  1295. } else {
  1296. targetPeerList = [targetPeerId];
  1297. }
  1298. targetPeerId = 'MCU';
  1299. }
  1300.  
  1301. if (dataInfo.dataType !== 'blob') {
  1302. // output: 1616
  1303. binaryChunkSize = self._CHUNK_DATAURL_SIZE;
  1304. chunkSize = self._CHUNK_DATAURL_SIZE;
  1305. binarySize = dataInfo.size;
  1306. } else if (window.webrtcDetectedBrowser === 'firefox') {
  1307. // output: 16384
  1308. binaryChunkSize = self._MOZ_CHUNK_FILE_SIZE * (4 / 3);
  1309. chunkSize = self._MOZ_CHUNK_FILE_SIZE;
  1310. } else {
  1311. // output: 65536
  1312. binaryChunkSize = parseInt((self._CHUNK_FILE_SIZE * (4 / 3)).toFixed(), 10);
  1313. chunkSize = self._CHUNK_FILE_SIZE;
  1314. }
  1315.  
  1316. var throwTransferErrorFn = function (message) {
  1317. // MCU targetPeerId case - list of peers
  1318. if (self._hasMCU) {
  1319. for (i = 0; i < targetPeerList.length; i++) {
  1320. var peerId = targetPeerList[i];
  1321. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
  1322. dataInfo.transferId, peerId, {
  1323. name: dataInfo.name,
  1324. size: dataInfo.size,
  1325. percentage: 0,
  1326. data: null,
  1327. dataType: dataInfo.dataType,
  1328. senderPeerId: self._user.sid,
  1329. timeout: dataInfo.timeout,
  1330. isPrivate: dataInfo.isPrivate
  1331. },{
  1332. message: message,
  1333. transferType: self.DATA_TRANSFER_TYPE.UPLOAD
  1334. });
  1335. }
  1336. } else {
  1337. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
  1338. dataInfo.transferId, targetPeerId, {
  1339. name: dataInfo.name,
  1340. size: dataInfo.size,
  1341. percentage: 0,
  1342. data: null,
  1343. dataType: dataInfo.dataType,
  1344. senderPeerId: self._user.sid,
  1345. timeout: dataInfo.timeout,
  1346. isPrivate: dataInfo.isPrivate
  1347. },{
  1348. message: message,
  1349. transferType: self.DATA_TRANSFER_TYPE.UPLOAD
  1350. });
  1351. }
  1352. };
  1353.  
  1354. var startTransferFn = function (targetId, channel) {
  1355. if (!hasSend) {
  1356. hasSend = true;
  1357. var payload = {
  1358. type: self._DC_PROTOCOL_TYPE.WRQ,
  1359. sender: self._user.sid,
  1360. agent: window.webrtcDetectedBrowser,
  1361. version: window.webrtcDetectedVersion,
  1362. name: dataInfo.name,
  1363. size: binarySize,
  1364. dataType: dataInfo.dataType,
  1365. chunkSize: binaryChunkSize,
  1366. timeout: dataInfo.timeout,
  1367. target: self._hasMCU ? targetPeerList : targetPeerId,
  1368. isPrivate: dataInfo.isPrivate
  1369. };
  1370.  
  1371. if (self._hasMCU) {
  1372. // if has MCU and is public, do not send individually
  1373. self._sendDataChannelMessage('MCU', payload, channel);
  1374. try {
  1375. var mainChannel = self._dataChannels.MCU.main.label;
  1376. self._setDataChannelTimeout('MCU', dataInfo.timeout, true, mainChannel);
  1377. } catch (error) {
  1378. log.error(['MCU', 'RTCDataChannel', 'MCU', 'Failed setting datachannel ' +
  1379. 'timeout for MCU'], error);
  1380. }
  1381. } else {
  1382. // if has MCU and is public, do not send individually
  1383. self._sendDataChannelMessage(targetId, payload, channel);
  1384. self._setDataChannelTimeout(targetId, dataInfo.timeout, true, channel);
  1385. }
  1386.  
  1387. }
  1388. };
  1389.  
  1390. log.log([targetPeerId, 'RTCDataChannel', targetChannel, 'Chunk size of data:'], {
  1391. chunkSize: chunkSize,
  1392. binaryChunkSize: binaryChunkSize,
  1393. transferId: dataInfo.transferId,
  1394. dataType: dataInfo.dataType
  1395. });
  1396.  
  1397.  
  1398. var supportMulti = false;
  1399. var peerAgent = (self._peerInformations[targetPeerId] || {}).agent || {};
  1400.  
  1401. if (!peerAgent && !peerAgent.name) {
  1402. log.error([targetPeerId, 'RTCDataChannel', targetChannel, 'Aborting transfer to peer ' +
  1403. 'as peer agent information for peer does not exists'], dataInfo);
  1404. throwTransferErrorFn('Peer agent information for peer does not exists');
  1405. return;
  1406. }
  1407.  
  1408. if (self._INTEROP_MULTI_TRANSFERS.indexOf(peerAgent.name) === -1) {
  1409.  
  1410. targetChannel = targetPeerId + '-' + dataInfo.transferId;
  1411. supportMulti = true;
  1412.  
  1413. if (!(self._dataChannels[targetPeerId] || {}).main) {
  1414. log.error([targetPeerId, 'RTCDataChannel', targetChannel,
  1415. 'Main datachannel does not exists'], dataInfo);
  1416. throwTransferErrorFn('Main datachannel does not exists');
  1417. return;
  1418.  
  1419. } else if (self._dataChannels[targetPeerId].main.readyState !==
  1420. self.DATA_CHANNEL_STATE.OPEN) {
  1421. log.error([targetPeerId, 'RTCDataChannel', targetChannel,
  1422. 'Main datachannel is not opened'], {
  1423. transferId: dataInfo.transferId,
  1424. readyState: self._dataChannels[targetPeerId].main.readyState
  1425. });
  1426. throwTransferErrorFn('Main datachannel is not opened');
  1427. return;
  1428. }
  1429.  
  1430. self._dataChannels[targetPeerId][targetChannel] =
  1431. self._createDataChannel(targetPeerId, self.DATA_CHANNEL_TYPE.DATA, null, targetChannel);
  1432.  
  1433. } else {
  1434. var ongoingTransfer = null;
  1435.  
  1436. if (self._uploadDataSessions[targetChannel]) {
  1437. ongoingTransfer = self.DATA_TRANSFER_TYPE.UPLOAD;
  1438. } else if (self._downloadDataSessions[targetChannel]) {
  1439. ongoingTransfer = self.DATA_TRANSFER_TYPE.DOWNLOAD;
  1440. }
  1441.  
  1442. if (ongoingTransfer) {
  1443. log.error([targetPeerId, 'RTCDataChannel', targetChannel, 'User have ongoing ' +
  1444. ongoingTransfer + ' transfer session with peer. Unable to send data'], dataInfo);
  1445. throwTransferErrorFn('Another ' + ongoingTransfer +
  1446. ' transfer is ongoing. Unable to send data.');
  1447. return;
  1448. }
  1449. }
  1450.  
  1451. if (dataInfo.dataType === 'blob') {
  1452. self._uploadDataTransfers[targetChannel] = self._chunkBlobData(data, chunkSize);
  1453. } else {
  1454. self._uploadDataTransfers[targetChannel] = self._chunkDataURL(data, chunkSize);
  1455. }
  1456.  
  1457. self._uploadDataSessions[targetChannel] = {
  1458. name: dataInfo.name,
  1459. size: binarySize,
  1460. isUpload: true,
  1461. senderPeerId: self._user.sid,
  1462. transferId: dataInfo.transferId,
  1463. percentage: 0,
  1464. timeout: dataInfo.timeout,
  1465. chunkSize: chunkSize,
  1466. dataType: dataInfo.dataType,
  1467. isPrivate: dataInfo.isPrivate
  1468. };
  1469.  
  1470. if (supportMulti) {
  1471. self._condition('dataChannelState', function () {
  1472. startTransferFn(targetPeerId, targetChannel);
  1473. }, function () {
  1474. return self._dataChannels[targetPeerId][targetChannel].readyState ===
  1475. self.DATA_CHANNEL_STATE.OPEN;
  1476. }, function (state) {
  1477. return state === self.DATA_CHANNEL_STATE.OPEN;
  1478. });
  1479. } else {
  1480. startTransferFn(targetChannel, targetChannel);
  1481. }
  1482.  
  1483. return targetChannel;
  1484. };
  1485.  
  1486. /**
  1487. * Function that handles the data received from Datachannel and
  1488. * routes to the relevant data transfer protocol handler.
  1489. * @method _dataChannelProtocolHandler
  1490. * @private
  1491. * @for Skylink
  1492. * @since 0.5.2
  1493. */
  1494. Skylink.prototype._dataChannelProtocolHandler = function(dataString, peerId, channelName, channelType) {
  1495. // PROTOCOL ESTABLISHMENT
  1496.  
  1497. if (!(this._peerInformations[peerId] || {}).agent) {
  1498. log.error([peerId, 'RTCDataChannel', channelName, 'Peer informations is missing during protocol ' +
  1499. 'handling. Dropping packet'], dataString);
  1500. return;
  1501. }
  1502.  
  1503. /*var useChannel = channelName;
  1504. var peerAgent = this._peerInformations[peerId].agent.name;
  1505.  
  1506. if (channelType === this.DATA_CHANNEL_TYPE.MESSAGING ||
  1507. this._INTEROP_MULTI_TRANSFERS[peerAgent] > -1) {
  1508. useChannel = peerId;
  1509. }*/
  1510.  
  1511. if (typeof dataString === 'string') {
  1512. var data = {};
  1513. try {
  1514. data = JSON.parse(dataString);
  1515. } catch (error) {
  1516. log.debug([peerId, 'RTCDataChannel', channelName, 'Received from peer ->'], {
  1517. type: 'DATA',
  1518. data: dataString
  1519. });
  1520. this._DATAProtocolHandler(peerId, dataString,
  1521. this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING, channelName);
  1522. return;
  1523. }
  1524. log.debug([peerId, 'RTCDataChannel', channelName, 'Received from peer ->'], {
  1525. type: data.type,
  1526. data: data
  1527. });
  1528. switch (data.type) {
  1529. case this._DC_PROTOCOL_TYPE.WRQ:
  1530. this._WRQProtocolHandler(peerId, data, channelName);
  1531. break;
  1532. case this._DC_PROTOCOL_TYPE.ACK:
  1533. this._ACKProtocolHandler(peerId, data, channelName);
  1534. break;
  1535. case this._DC_PROTOCOL_TYPE.ERROR:
  1536. this._ERRORProtocolHandler(peerId, data, channelName);
  1537. break;
  1538. case this._DC_PROTOCOL_TYPE.CANCEL:
  1539. this._CANCELProtocolHandler(peerId, data, channelName);
  1540. break;
  1541. case this._DC_PROTOCOL_TYPE.MESSAGE: // Not considered a protocol actually?
  1542. this._MESSAGEProtocolHandler(peerId, data, channelName);
  1543. break;
  1544. default:
  1545. log.error([peerId, 'RTCDataChannel', channelName, 'Unsupported message ->'], {
  1546. type: data.type,
  1547. data: data
  1548. });
  1549. }
  1550. }
  1551. };
  1552.  
  1553. /**
  1554. * Function that handles the "WRQ" data transfer protocol.
  1555. * @method _WRQProtocolHandler
  1556. * @private
  1557. * @for Skylink
  1558. * @since 0.5.2
  1559. */
  1560. Skylink.prototype._WRQProtocolHandler = function(peerId, data, channelName) {
  1561. var transferId = channelName + this._TRANSFER_DELIMITER + (new Date()).getTime();
  1562.  
  1563. log.log([peerId, 'RTCDataChannel', channelName,
  1564. 'Received file request from peer:'], data);
  1565.  
  1566. var name = data.name;
  1567. var binarySize = data.size;
  1568. var expectedSize = data.chunkSize;
  1569. var timeout = data.timeout;
  1570.  
  1571. this._downloadDataSessions[channelName] = {
  1572. transferId: transferId,
  1573. name: name,
  1574. isUpload: false,
  1575. senderPeerId: peerId,
  1576. size: binarySize,
  1577. percentage: 0,
  1578. dataType: data.dataType,
  1579. ackN: 0,
  1580. receivedSize: 0,
  1581. chunkSize: expectedSize,
  1582. timeout: timeout,
  1583. isPrivate: data.isPrivate
  1584. };
  1585. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.UPLOAD_REQUEST,
  1586. transferId, peerId, {
  1587. name: name,
  1588. size: binarySize,
  1589. percentage: 0,
  1590. data: null,
  1591. dataType: data.dataType,
  1592. senderPeerId: peerId,
  1593. timeout: timeout,
  1594. isPrivate: data.isPrivate
  1595. });
  1596. this._trigger('incomingDataRequest', transferId, peerId, {
  1597. name: name,
  1598. size: binarySize,
  1599. percentage: 0,
  1600. dataType: data.dataType,
  1601. senderPeerId: peerId,
  1602. timeout: timeout,
  1603. isPrivate: data.isPrivate
  1604. }, false);
  1605. };
  1606.  
  1607. /**
  1608. * Function that handles the "ACK" data transfer protocol.
  1609. * @method _ACKProtocolHandler
  1610. * @private
  1611. * @for Skylink
  1612. * @since 0.5.2
  1613. */
  1614. Skylink.prototype._ACKProtocolHandler = function(peerId, data, channelName) {
  1615. var self = this;
  1616. var ackN = data.ackN;
  1617. var transferStatus = self._uploadDataSessions[channelName];
  1618.  
  1619. if (!transferStatus) {
  1620. log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
  1621. 'upload data transfers is empty'], {
  1622. status: transferStatus,
  1623. data: data
  1624. });
  1625. return;
  1626. }
  1627.  
  1628. if (!this._uploadDataTransfers[channelName]) {
  1629. log.error([peerId, 'RTCDataChannel', channelName,
  1630. 'Ignoring data received as upload data transfers array is missing'], {
  1631. data: data
  1632. });
  1633. return;
  1634. }
  1635.  
  1636. //peerId = (peerId === 'MCU') ? data.sender : peerId;
  1637. var chunksLength = self._uploadDataTransfers[channelName].length;
  1638. var transferId = transferStatus.transferId;
  1639. var timeout = transferStatus.timeout;
  1640.  
  1641. self._clearDataChannelTimeout(peerId, true, channelName);
  1642. log.log([peerId, 'RTCDataChannel', channelName, 'ACK stage (' +
  1643. transferStatus.transferId + ') ->'], ackN + ' / ' + chunksLength);
  1644.  
  1645. if (ackN > -1) {
  1646. // Still uploading
  1647. if (ackN < chunksLength) {
  1648. var sendDataFn = function (base64BinaryString) {
  1649. var percentage = parseFloat((((ackN + 1) / chunksLength) * 100).toFixed(2), 10);
  1650.  
  1651. if (!self._uploadDataSessions[channelName]) {
  1652. log.error([peerId, 'RTCDataChannel', channelName,
  1653. 'Failed uploading as data session is empty'], {
  1654. status: transferStatus,
  1655. data: data
  1656. });
  1657. return;
  1658. }
  1659.  
  1660. self._uploadDataSessions[channelName].percentage = percentage;
  1661.  
  1662. self._sendDataChannelMessage(peerId, base64BinaryString, channelName);
  1663. self._setDataChannelTimeout(peerId, timeout, true, channelName);
  1664.  
  1665. // to prevent from firing upload = 100;
  1666. if (percentage !== 100) {
  1667. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOADING,
  1668. transferId, peerId, {
  1669. name: transferStatus.name,
  1670. size: transferStatus.size,
  1671. percentage: percentage,
  1672. data: null,
  1673. dataType: transferStatus.dataType,
  1674. senderPeerId: transferStatus.senderPeerId,
  1675. timeout: transferStatus.timeout,
  1676. isPrivate: transferStatus.isPrivate
  1677. });
  1678. }
  1679. };
  1680.  
  1681. if (transferStatus.dataType === 'blob') {
  1682. self._blobToBase64(self._uploadDataTransfers[channelName][ackN], sendDataFn);
  1683. } else {
  1684. sendDataFn(self._uploadDataTransfers[channelName][ackN]);
  1685. }
  1686. } else if (ackN === chunksLength) {
  1687. log.log([peerId, 'RTCDataChannel', channelName, 'Upload completed (' +
  1688. transferStatus.transferId + ')'], transferStatus);
  1689.  
  1690. self._trigger('dataTransferState',
  1691. self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED, transferId, peerId, {
  1692. name: transferStatus.name,
  1693. size: transferStatus.size,
  1694. percentage: 100,
  1695. data: null,
  1696. dataType: transferStatus.dataType,
  1697. senderPeerId: transferStatus.senderPeerId,
  1698. timeout: transferStatus.timeout,
  1699. isPrivate: transferStatus.isPrivate
  1700. });
  1701.  
  1702. var blob = null;
  1703.  
  1704. if (transferStatus.dataType === 'blob') {
  1705. blob = new Blob(self._uploadDataTransfers[channelName]);
  1706. } else {
  1707. blob = self._assembleDataURL(self._uploadDataTransfers[channelName]);
  1708. }
  1709.  
  1710. self._trigger('incomingData', blob, transferId, peerId, {
  1711. name: transferStatus.name,
  1712. size: transferStatus.size,
  1713. percentage: 100,
  1714. dataType: transferStatus.dataType,
  1715. senderPeerId: transferStatus.senderPeerId,
  1716. timeout: transferStatus.timeout,
  1717. isPrivate: transferStatus.isPrivate
  1718. }, true);
  1719. delete self._uploadDataTransfers[channelName];
  1720. delete self._uploadDataSessions[channelName];
  1721.  
  1722. // close datachannel after transfer
  1723. if (self._dataChannels[peerId] && self._dataChannels[peerId][channelName]) {
  1724. log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for upload transfer']);
  1725. self._closeDataChannel(peerId, channelName);
  1726. }
  1727. }
  1728. } else {
  1729. log.debug([peerId, 'RTCDataChannel', channelName, 'Upload rejected (' +
  1730. transferStatus.transferId + ')'], transferStatus);
  1731.  
  1732. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.REJECTED,
  1733. transferId, peerId, {
  1734. name: transferStatus.name, //self._uploadDataSessions[channelName].name,
  1735. size: transferStatus.size, //self._uploadDataSessions[channelName].size,
  1736. percentage: 0,
  1737. data: null,
  1738. dataType: transferStatus.dataType,
  1739. senderPeerId: transferStatus.senderPeerId,
  1740. timeout: transferStatus.timeout,
  1741. isPrivate: transferStatus.isPrivate
  1742. });
  1743. delete self._uploadDataTransfers[channelName];
  1744. delete self._uploadDataSessions[channelName];
  1745.  
  1746. // close datachannel if rejected
  1747. if (self._dataChannels[peerId] && self._dataChannels[peerId][channelName]) {
  1748. log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for upload transfer']);
  1749. self._closeDataChannel(peerId, channelName);
  1750. }
  1751. }
  1752. };
  1753.  
  1754. /**
  1755. * Function that handles the "MESSAGE" data transfer protocol.
  1756. * @method _MESSAGEProtocolHandler
  1757. * @private
  1758. * @for Skylink
  1759. * @since 0.5.2
  1760. */
  1761. Skylink.prototype._MESSAGEProtocolHandler = function(peerId, data, channelName) {
  1762. var targetMid = data.sender;
  1763. log.log([targetMid, 'RTCDataChannel', channelName,
  1764. 'Received P2P message from peer:'], data);
  1765. this._trigger('incomingMessage', {
  1766. content: data.data,
  1767. isPrivate: data.isPrivate,
  1768. isDataChannel: true,
  1769. targetPeerId: this._user.sid,
  1770. senderPeerId: targetMid
  1771. }, targetMid, this.getPeerInfo(targetMid), false);
  1772. };
  1773.  
  1774. /**
  1775. * Function that handles the "ERROR" data transfer protocol.
  1776. * @method _ERRORProtocolHandler
  1777. * @private
  1778. * @for Skylink
  1779. * @since 0.5.2
  1780. */
  1781. Skylink.prototype._ERRORProtocolHandler = function(peerId, data, channelName) {
  1782. var isUploader = data.isUploadError;
  1783. var transferStatus = (isUploader) ? this._uploadDataSessions[channelName] :
  1784. this._downloadDataSessions[channelName];
  1785.  
  1786. if (!transferStatus) {
  1787. log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
  1788. (isUploader ? 'upload' : 'download') + ' data session is empty'], data);
  1789. return;
  1790. }
  1791.  
  1792. var transferId = transferStatus.transferId;
  1793.  
  1794. log.error([peerId, 'RTCDataChannel', channelName,
  1795. 'Received an error from peer:'], data);
  1796. this._clearDataChannelTimeout(peerId, isUploader, channelName);
  1797. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
  1798. transferId, peerId, {
  1799. name: transferStatus.name,
  1800. size: transferStatus.size,
  1801. percentage: transferStatus.percentage,
  1802. data: null,
  1803. dataType: transferStatus.dataType,
  1804. senderPeerId: transferStatus.senderPeerId,
  1805. timeout: transferStatus.timeout,
  1806. isPrivate: transferStatus.isPrivate
  1807. }, {
  1808. message: data.content,
  1809. transferType: ((isUploader) ? this.DATA_TRANSFER_TYPE.UPLOAD :
  1810. this.DATA_TRANSFER_TYPE.DOWNLOAD)
  1811. });
  1812. };
  1813.  
  1814. /**
  1815. * Function that handles the "CANCEL" data transfer protocol.
  1816. * @method _CANCELProtocolHandler
  1817. * @private
  1818. * @for Skylink
  1819. * @since 0.5.0
  1820. */
  1821. Skylink.prototype._CANCELProtocolHandler = function(peerId, data, channelName) {
  1822. var isUpload = !!this._uploadDataSessions[channelName];
  1823. var isDownload = !!this._downloadDataSessions[channelName];
  1824. var transferStatus = (isUpload) ? this._uploadDataSessions[channelName] :
  1825. this._downloadDataSessions[channelName];
  1826.  
  1827. if (!transferStatus) {
  1828. log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
  1829. (isUpload ? 'upload' : 'download') + ' data session is empty'], data);
  1830. return;
  1831. }
  1832.  
  1833. var transferId = transferStatus.transferId;
  1834.  
  1835. log.log([peerId, 'RTCDataChannel', channelName,
  1836. 'Received file transfer cancel request:'], data);
  1837.  
  1838. this._clearDataChannelTimeout(peerId, isUpload, channelName);
  1839.  
  1840. try {
  1841. if (isUpload) {
  1842. delete this._uploadDataSessions[channelName];
  1843. delete this._uploadDataTransfers[channelName];
  1844. } else {
  1845. delete this._downloadDataSessions[channelName];
  1846. delete this._downloadDataTransfers[channelName];
  1847. }
  1848.  
  1849. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.CANCEL,
  1850. transferId, peerId, {
  1851. name: transferStatus.name,
  1852. size: transferStatus.size,
  1853. data: null,
  1854. dataType: transferStatus.dataType,
  1855. percentage: transferStatus.percentage,
  1856. senderPeerId: transferStatus.senderPeerId,
  1857. timeout: transferStatus.timeout,
  1858. isPrivate: transferStatus.isPrivate
  1859. }, {
  1860. message: data.content,
  1861. transferType: ((isUpload) ? this.DATA_TRANSFER_TYPE.UPLOAD :
  1862. this.DATA_TRANSFER_TYPE.DOWNLOAD)
  1863. });
  1864.  
  1865. log.log([peerId, 'RTCDataChannel', channelName,
  1866. 'Emptied file transfer session:'], data);
  1867.  
  1868. } catch (error) {
  1869. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
  1870. transferId, peerId, {
  1871. name: transferStatus.name,
  1872. size: transferStatus.size,
  1873. data: null,
  1874. dataType: transferStatus.dataType,
  1875. percentage: transferStatus.percentage,
  1876. senderPeerId: transferStatus.senderPeerId,
  1877. timeout: transferStatus.timeout,
  1878. isPrivate: transferStatus.isPrivate
  1879. }, {
  1880. message: 'Failed cancelling data request from peer',
  1881. transferType: ((isUpload) ? this.DATA_TRANSFER_TYPE.UPLOAD :
  1882. this.DATA_TRANSFER_TYPE.DOWNLOAD)
  1883. });
  1884.  
  1885. log.error([peerId, 'RTCDataChannel', channelName,
  1886. 'Failed emptying file transfer session:'], {
  1887. data: data,
  1888. error: error
  1889. });
  1890. }
  1891. };
  1892.  
  1893. /**
  1894. * Function that handles the data transfer chunk received.
  1895. * @method _DATAProtocolHandler
  1896. * @private
  1897. * @for Skylink
  1898. * @since 0.5.5
  1899. */
  1900. Skylink.prototype._DATAProtocolHandler = function(peerId, dataString, dataType, channelName) {
  1901. var chunk, error = '';
  1902. var transferStatus = this._downloadDataSessions[channelName];
  1903. log.log([peerId, 'RTCDataChannel', channelName,
  1904. 'Received data chunk from peer ->'], {
  1905. dataType: dataType,
  1906. data: dataString,
  1907. type: 'DATA'
  1908. });
  1909.  
  1910. if (!transferStatus) {
  1911. log.error([peerId, 'RTCDataChannel', channelName,
  1912. 'Ignoring data received as download data session is empty'], {
  1913. dataType: dataType,
  1914. data: dataString,
  1915. type: 'DATA'
  1916. });
  1917. return;
  1918. }
  1919.  
  1920. if (!this._downloadDataTransfers[channelName]) {
  1921. log.error([peerId, 'RTCDataChannel', channelName,
  1922. 'Ignoring data received as download data transfers array is missing'], {
  1923. dataType: dataType,
  1924. data: dataString,
  1925. type: 'DATA'
  1926. });
  1927. return;
  1928. }
  1929.  
  1930. var transferId = transferStatus.transferId;
  1931. var dataTransferType = transferStatus.dataType;
  1932. var receivedSize = 0;
  1933.  
  1934. this._clearDataChannelTimeout(peerId, false, channelName);
  1935.  
  1936. if (dataType === this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
  1937. if (dataTransferType === 'blob') {
  1938. chunk = this._base64ToBlob(dataString);
  1939. receivedSize = (chunk.size * (4 / 3));
  1940. } else {
  1941. chunk = dataString;
  1942. receivedSize = dataString.length;
  1943. }
  1944. } else if (dataType === this.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
  1945. chunk = new Blob(dataString);
  1946. } else if (dataType === this.DATA_TRANSFER_DATA_TYPE.BLOB) {
  1947. chunk = dataString;
  1948. } else {
  1949. error = 'Unhandled data exception: ' + dataType;
  1950. log.error([peerId, 'RTCDataChannel', channelName, 'Failed downloading data packets:'], {
  1951. dataType: dataType,
  1952. data: dataString,
  1953. type: 'DATA',
  1954. error: error
  1955. });
  1956. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
  1957. transferId, peerId, {
  1958. name: transferStatus.name,
  1959. size: transferStatus.size,
  1960. percentage: transferStatus.percentage,
  1961. data: null,
  1962. dataType: dataTransferType,
  1963. senderPeerId: transferStatus.senderPeerId,
  1964. timeout: transferStatus.timeout,
  1965. isPrivate: transferStatus.isPrivate
  1966. }, {
  1967. message: error,
  1968. transferType: this.DATA_TRANSFER_TYPE.DOWNLOAD
  1969. });
  1970. return;
  1971. }
  1972.  
  1973. log.log([peerId, 'RTCDataChannel', channelName,
  1974. 'Received and expected data chunk size (' + receivedSize + ' === ' +
  1975. transferStatus.chunkSize + ')'], {
  1976. dataType: dataType,
  1977. data: dataString,
  1978. receivedSize: receivedSize,
  1979. expectedSize: transferStatus.chunkSize,
  1980. type: 'DATA'
  1981. });
  1982.  
  1983. if (transferStatus.chunkSize >= receivedSize) {
  1984. this._downloadDataTransfers[channelName].push(chunk);
  1985. transferStatus.ackN += 1;
  1986. transferStatus.receivedSize += receivedSize;
  1987. var totalReceivedSize = transferStatus.receivedSize;
  1988. var percentage = parseFloat(((totalReceivedSize / transferStatus.size) * 100).toFixed(2), 10);
  1989.  
  1990. this._sendDataChannelMessage(peerId, {
  1991. type: this._DC_PROTOCOL_TYPE.ACK,
  1992. sender: this._user.sid,
  1993. ackN: transferStatus.ackN
  1994. }, channelName);
  1995.  
  1996. // update the percentage
  1997. this._downloadDataSessions[channelName].percentage = percentage;
  1998.  
  1999. if (transferStatus.chunkSize === receivedSize && percentage < 100) {
  2000. log.log([peerId, 'RTCDataChannel', channelName,
  2001. 'Transfer in progress ACK n (' + transferStatus.ackN + ')'], {
  2002. dataType: dataType,
  2003. data: dataString,
  2004. ackN: transferStatus.ackN,
  2005. type: 'DATA'
  2006. });
  2007. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOADING,
  2008. transferId, peerId, {
  2009. name: transferStatus.name,
  2010. size: transferStatus.size,
  2011. percentage: percentage,
  2012. data: null,
  2013. dataType: dataTransferType,
  2014. senderPeerId: transferStatus.senderPeerId,
  2015. timeout: transferStatus.timeout,
  2016. isPrivate: transferStatus.isPrivate
  2017. });
  2018. this._setDataChannelTimeout(peerId, transferStatus.timeout, false, channelName);
  2019.  
  2020. if (!this._downloadDataSessions[channelName]) {
  2021. log.error([peerId, 'RTCDataChannel', channelName,
  2022. 'Failed downloading as data session is empty'], {
  2023. dataType: dataType,
  2024. data: dataString,
  2025. type: 'DATA'
  2026. });
  2027. return;
  2028. }
  2029.  
  2030. this._downloadDataSessions[channelName].info = transferStatus;
  2031.  
  2032. } else {
  2033. log.log([peerId, 'RTCDataChannel', channelName,
  2034. 'Download complete'], {
  2035. dataType: dataType,
  2036. data: dataString,
  2037. type: 'DATA',
  2038. transferInfo: transferStatus
  2039. });
  2040.  
  2041. var blob = null;
  2042.  
  2043. if (dataTransferType === 'blob') {
  2044. blob = new Blob(this._downloadDataTransfers[channelName]);
  2045. } else {
  2046. blob = this._assembleDataURL(this._downloadDataTransfers[channelName]);
  2047. }
  2048. this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED,
  2049. transferId, peerId, {
  2050. name: transferStatus.name,
  2051. size: transferStatus.size,
  2052. percentage: 100,
  2053. data: blob,
  2054. dataType: dataTransferType,
  2055. senderPeerId: transferStatus.senderPeerId,
  2056. timeout: transferStatus.timeout,
  2057. isPrivate: transferStatus.isPrivate
  2058. });
  2059.  
  2060. this._trigger('incomingData', blob, transferId, peerId, {
  2061. name: transferStatus.name,
  2062. size: transferStatus.size,
  2063. percentage: 100,
  2064. dataType: dataTransferType,
  2065. senderPeerId: transferStatus.senderPeerId,
  2066. timeout: transferStatus.timeout,
  2067. isPrivate: transferStatus.isPrivate
  2068. }, false);
  2069.  
  2070. delete this._downloadDataTransfers[channelName];
  2071. delete this._downloadDataSessions[channelName];
  2072.  
  2073. log.log([peerId, 'RTCDataChannel', channelName,
  2074. 'Converted to Blob as download'], {
  2075. dataType: dataType,
  2076. data: dataString,
  2077. type: 'DATA',
  2078. transferInfo: transferStatus
  2079. });
  2080.  
  2081. // close datachannel after transfer
  2082. if (this._dataChannels[peerId] && this._dataChannels[peerId][channelName]) {
  2083. log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for download transfer']);
  2084. this._closeDataChannel(peerId, channelName);
  2085. }
  2086. }
  2087.  
  2088. } else {
  2089. error = 'Packet not match - [Received]' + receivedSize +
  2090. ' / [Expected]' + transferStatus.chunkSize;
  2091.  
  2092. this._trigger('dataTransferState',
  2093. this.DATA_TRANSFER_STATE.ERROR, transferId, peerId, {
  2094. name: transferStatus.name,
  2095. size: transferStatus.size,
  2096. percentage: transferStatus.percentage,
  2097. data: null,
  2098. dataType: dataTransferType,
  2099. senderPeerId: transferStatus.senderPeerId,
  2100. timeout: transferStatus.timeout,
  2101. isPrivate: transferStatus.isPrivate
  2102. }, {
  2103. message: error,
  2104. transferType: this.DATA_TRANSFER_TYPE.DOWNLOAD
  2105. });
  2106.  
  2107. log.error([peerId, 'RTCDataChannel', channelName,
  2108. 'Failed downloading data packets:'], {
  2109. dataType: dataType,
  2110. data: dataString,
  2111. type: 'DATA',
  2112. transferInfo: transferStatus,
  2113. error: error
  2114. });
  2115. }
  2116. };
  2117.  
  2118. /**
  2119. * Function that start the data transfer with the list of targeted Peer IDs provided.
  2120. * At this stage, it will open a new Datachannel connection if simultaneous data transfers is
  2121. * supported by Peer, or it will using the messaging type Datachannel connection.
  2122. * Note that 1 data transfer can occur at a time in 1 Datachannel connection.
  2123. * @method _startDataTransfer
  2124. * @private
  2125. * @for Skylink
  2126. * @since 0.6.1
  2127. */
  2128. Skylink.prototype._startDataTransfer = function(data, dataInfo, listOfPeers, callback) {
  2129. var self = this;
  2130. var error = '';
  2131. var noOfPeersSent = 0;
  2132. var transferId = dataInfo.transferId;
  2133. var dataType = dataInfo.dataType;
  2134. var isPrivate = dataInfo.isPrivate;
  2135. var i;
  2136. var peerId;
  2137.  
  2138. // for callback
  2139. var listOfPeersTransferState = {};
  2140. var transferSuccess = true;
  2141. var listOfPeersTransferErrors = {};
  2142. var listOfPeersChannels = {};
  2143. var successfulPeerTransfers = [];
  2144.  
  2145. var triggerCallbackFn = function () {
  2146. for (i = 0; i < listOfPeers.length; i++) {
  2147. var transferPeerId = listOfPeers[i];
  2148.  
  2149. if (!listOfPeersTransferState[transferPeerId]) {
  2150. // if error, make as false and break
  2151. transferSuccess = false;
  2152. break;
  2153. }
  2154. }
  2155.  
  2156. if (transferSuccess) {
  2157. log.log([null, 'RTCDataChannel', transferId, 'Firing success callback for data transfer'], dataInfo);
  2158. // should we even support this? maybe keeping to not break older impl
  2159. if (listOfPeers.length === 1 && isPrivate) {
  2160. callback(null,{
  2161. state: self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED,
  2162. peerId: listOfPeers[0],
  2163. listOfPeers: listOfPeers,
  2164. transferId: transferId,
  2165. isPrivate: isPrivate, // added new flag to indicate privacy
  2166. transferInfo: dataInfo
  2167. });
  2168. } else {
  2169. callback(null,{
  2170. state: null,
  2171. peerId: null,
  2172. transferId: transferId,
  2173. listOfPeers: listOfPeers,
  2174. isPrivate: isPrivate, // added new flag to indicate privacy
  2175. transferInfo: dataInfo
  2176. });
  2177. }
  2178. } else {
  2179. log.log([null, 'RTCDataChannel', transferId, 'Firing failure callback for data transfer'], dataInfo);
  2180.  
  2181. // should we even support this? maybe keeping to not break older impl
  2182. if (listOfPeers.length === 1 && isPrivate) {
  2183. callback({
  2184. state: self.DATA_TRANSFER_STATE.ERROR,
  2185. error: listOfPeersTransferErrors[listOfPeers[0]],
  2186. peerId: listOfPeers[0],
  2187. transferId: transferId,
  2188. transferErrors: listOfPeersTransferErrors,
  2189. transferInfo: dataInfo,
  2190. isPrivate: isPrivate, // added new flag to indicate privacy
  2191. listOfPeers: listOfPeers
  2192. }, null);
  2193. } else {
  2194. callback({
  2195. state: null,
  2196. peerId: null,
  2197. error: null,
  2198. transferId: transferId,
  2199. listOfPeers: listOfPeers,
  2200. isPrivate: isPrivate, // added new flag to indicate privacy
  2201. transferInfo: dataInfo,
  2202. transferErrors: listOfPeersTransferErrors
  2203. }, null);
  2204. }
  2205. }
  2206. };
  2207.  
  2208. for (i = 0; i < listOfPeers.length; i++) {
  2209. peerId = listOfPeers[i];
  2210.  
  2211. if (peerId === 'MCU') {
  2212. continue;
  2213. }
  2214.  
  2215. if (self._dataChannels[peerId] && self._dataChannels[peerId].main) {
  2216. log.log([peerId, 'RTCDataChannel', null, 'Sending blob data ->'], dataInfo);
  2217.  
  2218. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_STARTED,
  2219. transferId, peerId, {
  2220. name: dataInfo.name,
  2221. size: dataInfo.size,
  2222. percentage: 0,
  2223. data: data,
  2224. dataType: dataType,
  2225. senderPeerId: self._user.sid,
  2226. timeout: dataInfo.timeout,
  2227. isPrivate: isPrivate
  2228. });
  2229.  
  2230. self._trigger('incomingDataRequest', transferId, peerId, {
  2231. name: dataInfo.name,
  2232. size: dataInfo.size,
  2233. percentage: 0,
  2234. dataType: dataType,
  2235. senderPeerId: self._user.sid,
  2236. timeout: dataInfo.timeout,
  2237. isPrivate: isPrivate
  2238. }, true);
  2239.  
  2240. if (!self._hasMCU) {
  2241. listOfPeersChannels[peerId] =
  2242. self._sendBlobDataToPeer(data, dataInfo, peerId);
  2243. } else {
  2244. listOfPeersChannels[peerId] = self._dataChannels[peerId].main.label;
  2245. }
  2246.  
  2247. noOfPeersSent++;
  2248.  
  2249. } else {
  2250. error = 'Datachannel does not exist. Unable to start data transfer with peer';
  2251. log.error([peerId, 'RTCDataChannel', null, error]);
  2252. listOfPeersTransferErrors[peerId] = error;
  2253. }
  2254. }
  2255.  
  2256. // if has MCU
  2257. if (self._hasMCU) {
  2258. self._sendBlobDataToPeer(data, dataInfo, listOfPeers, isPrivate, transferId);
  2259. }
  2260.  
  2261. if (noOfPeersSent === 0) {
  2262. error = 'Failed sending data as there is no available datachannels to send data';
  2263.  
  2264. for (i = 0; i < listOfPeers.length; i++) {
  2265. peerId = listOfPeers[i];
  2266.  
  2267. self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
  2268. transferId, peerId, {
  2269. name: dataInfo.name,
  2270. size: dataInfo.size,
  2271. data: null,
  2272. dataType: dataType,
  2273. percentage: 0,
  2274. senderPeerId: self._user.sid,
  2275. timeout: dataInfo.timeout,
  2276. isPrivate: isPrivate
  2277. }, {
  2278. message: error,
  2279. transferType: self.DATA_TRANSFER_TYPE.UPLOAD
  2280. });
  2281.  
  2282. listOfPeersTransferErrors[peerId] = error;
  2283. }
  2284.  
  2285. log.error([null, 'RTCDataChannel', null, error]);
  2286. self._uploadDataTransfers = [];
  2287. self._uploadDataSessions = [];
  2288.  
  2289. transferSuccess = false;
  2290.  
  2291. if (typeof callback === 'function') {
  2292. triggerCallbackFn();
  2293. }
  2294. return;
  2295. }
  2296.  
  2297. if (typeof callback === 'function') {
  2298. var dataChannelStateFn = function(state, transferringPeerId, errorObj, channelName, channelType){
  2299. // check if error or closed halfway, if so abort
  2300. if (state === self.DATA_CHANNEL_STATE.ERROR &&
  2301. state === self.DATA_CHANNEL_STATE.CLOSED &&
  2302. listOfPeersChannels[peerId] === channelName) {
  2303. // if peer has already been inside, ignore
  2304. if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
  2305. listOfPeersTransferState[transferringPeerId] = false;
  2306. listOfPeersTransferErrors[transferringPeerId] = errorObj;
  2307.  
  2308. log.error([transferringPeerId, 'RTCDataChannel', null,
  2309. 'Data channel state has met a failure state for peer (datachannel) ->'], {
  2310. state: state,
  2311. error: errorObj
  2312. });
  2313. }
  2314. }
  2315.  
  2316. if (Object.keys(listOfPeersTransferState).length === listOfPeers.length) {
  2317. self.off('dataTransferState', dataTransferStateFn);
  2318. self.off('dataChannelState', dataChannelStateFn);
  2319.  
  2320. log.log([null, 'RTCDataChannel', transferId,
  2321. 'Transfer states have been gathered completely in dataChannelState'], state);
  2322.  
  2323. triggerCallbackFn();
  2324. }
  2325. };
  2326.  
  2327. var dataTransferStateFn = function(state, stateTransferId, transferringPeerId, transferInfo, errorObj){
  2328. // check if transfer is related to this transfer
  2329. if (stateTransferId === transferId) {
  2330. // check if state upload has completed
  2331. if (state === self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED) {
  2332.  
  2333. log.debug([transferringPeerId, 'RTCDataChannel', stateTransferId,
  2334. 'Data transfer state has met a success state for peer ->'], state);
  2335.  
  2336. // if peer has already been inside, ignore
  2337. if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
  2338. listOfPeersTransferState[transferringPeerId] = true;
  2339. }
  2340. } else if(state === self.DATA_TRANSFER_STATE.REJECTED ||
  2341. state === self.DATA_TRANSFER_STATE.CANCEL ||
  2342. state === self.DATA_TRANSFER_STATE.ERROR) {
  2343.  
  2344. if (state === self.DATA_TRANSFER_STATE.REJECTED) {
  2345. errorObj = new Error('Peer has rejected data transfer request');
  2346. }
  2347.  
  2348. log.error([transferringPeerId, 'RTCDataChannel', stateTransferId,
  2349. 'Data transfer state has met a failure state for peer ->'], {
  2350. state: state,
  2351. error: errorObj
  2352. });
  2353.  
  2354. // if peer has already been inside, ignore
  2355. if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
  2356. listOfPeersTransferState[transferringPeerId] = false;
  2357. listOfPeersTransferErrors[transferringPeerId] = errorObj;
  2358. }
  2359. }
  2360. }
  2361.  
  2362. if (Object.keys(listOfPeersTransferState).length === listOfPeers.length) {
  2363. self.off('dataTransferState', dataTransferStateFn);
  2364. self.off('dataChannelState', dataChannelStateFn);
  2365.  
  2366. log.log([null, 'RTCDataChannel', stateTransferId,
  2367. 'Transfer states have been gathered completely in dataTransferState'], state);
  2368.  
  2369. triggerCallbackFn();
  2370. }
  2371. };
  2372. self.on('dataTransferState', dataTransferStateFn);
  2373. self.on('dataChannelState', dataChannelStateFn);
  2374. }
  2375. };
  2376.