- /**
- * Function that starts an uploading data transfer from User to Peers.
- * @method sendBlobData
- * @param {Blob} data The Blob object.
- * @param {Number} [timeout=60] The timeout to wait for response from Peer.
- * @param {String|Array} [targetPeerId] The target Peer ID to start data transfer with.
- * - When provided as an Array, it will start uploading data transfers with all connections
- * with all the Peer IDs provided.
- * - When not provided, it will start uploading data transfers with all the currently connected Peers in the Room.
- * @param {Boolean} [sendChunksAsBinary=false] <blockquote class="info">
- * Note that this is currently not supported for MCU enabled Peer connections or Peer connections connecting from
- * Android, iOS and Linux SDKs. This would fallback to <code>transferInfo.chunkType</code> to
- * <code>BINARY_STRING</code> when MCU is connected. </blockquote> The flag if data transfer
- * binary data chunks should not be encoded as Base64 string during data transfers.
- * @param {Function} [callback] The callback function fired when request has completed.
- * <small>Function parameters signature is <code>function (error, success)</code></small>
- * <small>Function request completion is determined by the <a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggering <code>state</code> parameter payload
- * as <code>UPLOAD_COMPLETED</code> for all Peers targeted for request success.</small>
- * @param {JSON} callback.error The error result in request.
- * <small>Defined as <code>null</code> when there are no errors in request</small>
- * @param {String} callback.error.transferId The data transfer ID.
- * <small>Defined as <code>null</code> when <code>sendBlobData()</code> fails to start data transfer.</small>
- * @param {Array} callback.error.listOfPeers The list Peer IDs targeted for the data transfer.
- * @param {JSON} callback.error.transferErrors The list of data transfer errors.
- * @param {Error|String} callback.error.transferErrors.#peerId The data transfer error associated
- * with the Peer ID defined in <code>#peerId</code> property.
- * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
- * are no Peer connections to start data transfer with.</small>
- * @param {JSON} callback.error.transferInfo The data transfer information.
- * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
- * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
- * <code>percentage</code> and <code>data</code> property.</small>
- * @param {JSON} callback.success The success result in request.
- * <small>Defined as <code>null</code> when there are errors in request</small>
- * @param {String} callback.success.transferId The data transfer ID.
- * @param {Array} callback.success.listOfPeers The list Peer IDs targeted for the data transfer.
- * @param {JSON} callback.success.transferInfo The data transfer information.
- * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
- * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
- * <code>percentage</code> property and <code>data</code>.</small>
- * @trigger <ol class="desc-seq">
- * <li>Checks if User is in Room. <ol>
- * <li>If User is not in Room: <ol><li><a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if there is any available Datachannel connections. <ol>
- * <li>If User is not in Room: <ol><li><a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if provided <code>data</code> parameter is valid. <ol>
- * <li>If it is invalid: <ol><li><a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
- * <li>If Peer connection or session does not exists: <ol><li><a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If Peer connection is not stable: <small>The stable state can be checked with <a href="#event_peerConnectionState">
- * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>STABLE</code>
- * for Peer.</small> <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If Peer connection messaging Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
- * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>method</a> and connected: <ol>
- * <li>If MCU Peer connection is not stable: <small>The stable state can be checked with <a href="#event_peerConnectionState">
- * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>STABLE</code>
- * and <code>peerId</code> value as <code>"MCU"</code> for MCU Peer.</small>
- * <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If MCU Peer connection messaging Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code>, <code>peerId</code> value as <code>"MCU"</code>
- * and <code>channelType</code> as <code>MESSAGING</code> for MCU Peer.</small>
- * <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if should open a new data Datachannel.<ol>
- * <li>If Peer supports simultaneous data transfer, open new data Datachannel: <small>If MCU is connected,
- * this opens a new data Datachannel with MCU Peer with all the Peers IDs information that supports
- * simultaneous data transfers targeted for the data transfer session instead of opening new data Datachannel
- * with all Peers targeted for the data transfer session.</small> <ol>
- * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter
- * payload <code>state</code> as <code>CONNECTING</code> and <code>channelType</code> as <code>DATA</code>.
- * <small>Note that there is no timeout to wait for parameter payload <code>state</code> to be
- * <code>OPEN</code>.</small></li>
- * <li>If Datachannel has been created and opened successfully: <ol>
- * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter payload
- * <code>state</code> as <code>OPEN</code> and <code>channelType</code> as <code>DATA</code>.</li></ol></li>
- * <li>Else: <ol><li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>CREATE_ERROR</code> and <code>channelType</code> as
- * <code>DATA</code>.</li><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li></ol></li><li>Else: <small>If MCU is connected,
- * this uses the messaging Datachannel with MCU Peer with all the Peers IDs information that supports
- * simultaneous data transfers targeted for the data transfer session instead of using the messaging Datachannels
- * with all Peers targeted for the data transfer session.</small> <ol><li>If messaging Datachannel connection has a
- * data transfer in-progress: <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li><li>If there is any conflicting <a href="#method_streamData"><code>streamData()</code>
- * method</a> data streaming session: <small>If <code>sendChunksAsBinary</code> is provided as <code>true</code>,
- * it cannot start if existing data streaming session is expected binary data chunks, and if provided as
- * <code>false</code>, or method invoked is <a href="#method_sendURLData"><code>sendURLData()</code> method</a>,
- * or Peer is using string data chunks fallback due to its support despite provided as <code>true</code>,
- * it cannot start if existing data streaming session is expected string data chunks.</small><ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li></li></ol></ol></li></ol></li>
- * <li>Starts the data transfer to Peer. <ol>
- * <li><a href="#event_incomingDataRequest"><code>incomingDataRequest</code> event</a> triggers.</li>
- * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>USER_UPLOAD_REQUEST</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>UPLOAD_REQUEST</code>.</li>
- * <li>Peer invokes <a href="#method_acceptDataTransfer"><code>acceptDataTransfer()</code> method</a>. <ol>
- * <li>If parameter <code>accept</code> value is <code>true</code>: <ol>
- * <li>User starts upload data transfer to Peer. <ol>
- * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>UPLOAD_STARTED</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>DOWNLOAD_STARTED</code>.</li></ol></li>
- * <li>If Peer / User invokes <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>: <ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>CANCEL</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If data transfer has timeout errors: <ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>Checks for Peer connection and Datachannel connection during data transfer: <ol>
- * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>
- * method</a> and connected: <ol>
- * <li>If MCU Datachannel has closed abruptly during data transfer: <ol>
- * <small>This can be checked with <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
- * triggering parameter payload <code>state</code> as <code>CLOSED</code>, <code>peerId</code> value as
- * <code>"MCU"</code> and <code>channelType</code> as <code>DATA</code> for targeted Peers that supports simultaneous
- * data transfer or <code>MESSAGING</code> for targeted Peers that do not support it.</small> <ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>If MCU Peer connection has changed from not being stable: <ol>
- * <small>This can be checked with <a href="#event_peerConnectionState"><code>peerConnection</code> event</a>
- * triggering parameter payload <code>state</code> as not <code>STABLE</code>, <code>peerId</code> value as
- * <code>"MCU"</code>.</small> <ol><li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>If Peer connection has changed from not being stable: <ol>
- * <small>This can be checked with <a href="#event_peerConnectionState"><code>peerConnection</code> event</a>
- * triggering parameter payload <code>state</code> as not <code>STABLE</code>.</small> <ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
- * <li>Else: <ol><li>If Datachannel has closed abruptly during data transfer:
- * <small>This can be checked with <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
- * triggering parameter payload <code>state</code> as <code>CLOSED</code> and <code>channelType</code>
- * as <code>DATA</code> for Peer that supports simultaneous data transfer or <code>MESSAGING</code>
- * for Peer that do not support it.</small> <ol>
- * <li><a href="#event_dataTransferState"><code>dataTransferState</code> event</a> triggers parameter
- * <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
- * <li>If data transfer is still progressing: <ol>
- * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>UPLOADING</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>DOWNLOADING</code>.</li></ol></li>
- * <li>If data transfer has completed <ol>
- * <li><a href="#event_incomingData"><code>incomingData</code> event</a> triggers.</li>
- * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>UPLOAD_COMPLETED</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>DOWNLOAD_COMPLETED</code>.</li></ol></li></ol></li>
- * <li>If parameter <code>accept</code> value is <code>false</code>: <ol>
- * <li><em>For User only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>REJECTED</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataTransferState"><code>dataTransferState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>USER_REJECTED</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li></ol>
- * @example
- * <body>
- * <input type="radio" name="timeout" onchange="setTransferTimeout(0)"> 1s timeout (Default)
- * <input type="radio" name="timeout" onchange="setTransferTimeout(120)"> 2s timeout
- * <input type="radio" name="timeout" onchange="setTransferTimeout(300)"> 5s timeout
- * <hr>
- * <input type="file" onchange="uploadFile(this.files[0], this.getAttribute('data'))" data="peerId">
- * <input type="file" onchange="uploadFileGroup(this.files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB">
- * <input type="file" onchange="uploadFileAll(this.files[0])" data="">
- * <script>
- * var transferTimeout = 0;
- *
- * function setTransferTimeout (timeout) {
- * transferTimeout = timeout;
- * }
- *
- * // Example 1: Upload data to a Peer
- * function uploadFile (file, peerId) {
- * var cb = function (error, success) {
- * if (error) return;
- * console.info("File has been transferred to '" + peerId + "' successfully");
- * };
- * if (transferTimeout > 0) {
- * skylinkDemo.sendBlobData(file, peerId, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendBlobData(file, peerId, cb);
- * }
- * }
- *
- * // Example 2: Upload data to a list of Peers
- * function uploadFileGroup (file, peerIds) {
- * var cb = function (error, success) {
- * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
- * var listOfPeersErrors = error ? error.transferErrors : {};
- * for (var i = 0; i < listOfPeers.length; i++) {
- * if (listOfPeersErrors[listOfPeers[i]]) {
- * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
- * } else {
- * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
- * }
- * }
- * };
- * if (transferTimeout > 0) {
- * skylinkDemo.sendBlobData(file, peerIds, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendBlobData(file, peerIds, cb);
- * }
- * }
- *
- * // Example 2: Upload data to a list of Peers
- * function uploadFileAll (file) {
- * var cb = function (error, success) {
- * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
- * var listOfPeersErrors = error ? error.transferErrors : {};
- * for (var i = 0; i < listOfPeers.length; i++) {
- * if (listOfPeersErrors[listOfPeers[i]]) {
- * console.error("Failed file transfer to '" + listOfPeers[i] + "'");
- * } else {
- * console.info("File has been transferred to '" + listOfPeers[i] + "' successfully");
- * }
- * }
- * };
- * if (transferTimeout > 0) {
- * skylinkDemo.sendBlobData(file, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendBlobData(file, cb);
- * }
- * }
- * </script>
- * </body>
- * @for Skylink
- * @since 0.5.5
- */
- Skylink.prototype.sendBlobData = function(data, timeout, targetPeerId, sendChunksAsBinary, callback) {
- this._startDataTransfer(data, timeout, targetPeerId, sendChunksAsBinary, callback, 'blob');
- };
-
- /**
- * Function that starts an uploading string data transfer from User to Peers.
- * @method sendURLData
- * @param {String} data The data string to transfer to Peer.
- * @param {Number} [timeout=60] The timeout to wait for response from Peer.
- * @param {String|Array} [targetPeerId] The target Peer ID to start data transfer with.
- * - When provided as an Array, it will start uploading data transfers with all connections
- * with all the Peer IDs provided.
- * - When not provided, it will start uploading data transfers with all the currently connected Peers in the Room.
- * @param {Function} [callback] The callback function fired when request has completed.
- * <small>Function parameters signature is <code>function (error, success)</code></small>
- * <small>Function request completion is determined by the <a href="#event_dataTransferState">
- * <code>dataTransferState</code> event</a> triggering <code>state</code> parameter payload
- * as <code>UPLOAD_COMPLETED</code> for all Peers targeted for request success.</small>
- * @param {JSON} callback.error The error result in request.
- * <small>Defined as <code>null</code> when there are no errors in request</small>
- * @param {String} callback.error.transferId The data transfer ID.
- * <small>Defined as <code>null</code> when <code>sendURLData()</code> fails to start data transfer.</small>
- * @param {Array} callback.error.listOfPeers The list Peer IDs targeted for the data transfer.
- * @param {JSON} callback.error.transferErrors The list of data transfer errors.
- * @param {Error|String} callback.error.transferErrors.#peerId The data transfer error associated
- * with the Peer ID defined in <code>#peerId</code> property.
- * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
- * are no Peer connections to start data transfer with.</small>
- * @param {JSON} callback.error.transferInfo The data transfer information.
- * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
- * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
- * <code>percentage</code> property and <code>data</code>.</small>
- * @param {JSON} callback.success The success result in request.
- * <small>Defined as <code>null</code> when there are errors in request</small>
- * @param {String} callback.success.transferId The data transfer ID.
- * @param {Array} callback.success.listOfPeers The list Peer IDs targeted for the data transfer.
- * @param {JSON} callback.success.transferInfo The data transfer information.
- * <small>Object signature matches the <code>transferInfo</code> parameter payload received in the
- * <a href="#event_dataTransferState"><code>dataTransferState</code> event</a> except without the
- * <code>percentage</code> property and <code>data</code>.</small>
- * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
- * <code>sendBlobData()</code> method</a>.</small>
- * @example
- * <body>
- * <input type="radio" name="timeout" onchange="setTransferTimeout(0)"> 1s timeout (Default)
- * <input type="radio" name="timeout" onchange="setTransferTimeout(120)"> 2s timeout
- * <input type="radio" name="timeout" onchange="setTransferTimeout(300)"> 5s timeout
- * <hr>
- * <input type="file" onchange="showImage(this.files[0], this.getAttribute('data'))" data="peerId">
- * <input type="file" onchange="showImageGroup(this.files[0], this.getAttribute('data').split(',')))" data="peerIdA,peerIdB">
- * <input type="file" onchange="showImageAll(this.files[0])" data="">
- * <image id="target-1" src="">
- * <image id="target-2" src="">
- * <image id="target-3" src="">
- * <script>
- * var transferTimeout = 0;
- *
- * function setTransferTimeout (timeout) {
- * transferTimeout = timeout;
- * }
- *
- * function retrieveImageDataURL(file, cb) {
- * var fr = new FileReader();
- * fr.onload = function () {
- * cb(fr.result);
- * };
- * fr.readAsDataURL(files[0]);
- * }
- *
- * // Example 1: Send image data URL to a Peer
- * function showImage (file, peerId) {
- * var cb = function (error, success) {
- * if (error) return;
- * console.info("Image has been transferred to '" + peerId + "' successfully");
- * };
- * retrieveImageDataURL(file, function (str) {
- * if (transferTimeout > 0) {
- * skylinkDemo.sendURLData(str, peerId, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendURLData(str, peerId, cb);
- * }
- * document.getElementById("target-1").src = str;
- * });
- * }
- *
- * // Example 2: Send image data URL to a list of Peers
- * function showImageGroup (file, peerIds) {
- * var cb = function (error, success) {
- * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
- * var listOfPeersErrors = error ? error.transferErrors : {};
- * for (var i = 0; i < listOfPeers.length; i++) {
- * if (listOfPeersErrors[listOfPeers[i]]) {
- * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
- * } else {
- * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
- * }
- * }
- * };
- * retrieveImageDataURL(file, function (str) {
- * if (transferTimeout > 0) {
- * skylinkDemo.sendURLData(str, peerIds, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendURLData(str, peerIds, cb);
- * }
- * document.getElementById("target-2").src = str;
- * });
- * }
- *
- * // Example 2: Send image data URL to a list of Peers
- * function uploadFileAll (file) {
- * var cb = function (error, success) {
- * var listOfPeers = error ? error.listOfPeers : success.listOfPeers;
- * var listOfPeersErrors = error ? error.transferErrors : {};
- * for (var i = 0; i < listOfPeers.length; i++) {
- * if (listOfPeersErrors[listOfPeers[i]]) {
- * console.error("Failed image transfer to '" + listOfPeers[i] + "'");
- * } else {
- * console.info("Image has been transferred to '" + listOfPeers[i] + "' successfully");
- * }
- * }
- * };
- * retrieveImageDataURL(file, function (str) {
- * if (transferTimeout > 0) {
- * skylinkDemo.sendURLData(str, transferTimeout, cb);
- * } else {
- * skylinkDemo.sendURLData(str, cb);
- * }
- * document.getElementById("target-3").src = str;
- * });
- * }
- * </script>
- * </body>
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype.sendURLData = function(data, timeout, targetPeerId, callback) {
- this._startDataTransfer(data, timeout, targetPeerId, callback, null, 'data');
- };
-
- /**
- * Function that accepts or rejects an upload data transfer request from Peer to User.
- * @method acceptDataTransfer
- * @param {String} peerId The Peer ID.
- * @param {String} transferId The data transfer ID.
- * @param {Boolean} [accept=false] The flag if User accepts the upload data transfer request from Peer.
- * @example
- * // Example 1: Accept Peer upload data transfer request
- * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
- * if (!isSelf) {
- * skylinkDemo.acceptDataTransfer(peerId, transferId, true);
- * }
- * });
- *
- * // Example 2: Reject Peer upload data transfer request
- * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
- * if (!isSelf) {
- * skylinkDemo.acceptDataTransfer(peerId, transferId, false);
- * }
- * });
- * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
- * <code>sendBlobData()</code> method</a> after <code>acceptDataTransfer()</code> method is invoked.</small>
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype.respondBlobRequest =
- Skylink.prototype.acceptDataTransfer = function (peerId, transferId, accept) {
- var self = this;
-
- if (typeof transferId !== 'string' && typeof peerId !== 'string') {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
- 'data transfer ID or peer ID is not provided']);
- return;
- }
-
- if (!self._dataChannels[peerId]) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
- 'Peer does not have any Datachannel connections']);
- return;
- }
-
- if (!self._dataTransfers[transferId]) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting accept data transfer as ' +
- 'invalid transfer ID is provided']);
- return;
- }
-
- // Check Datachannel property in _dataChannels[peerId] list
- var channelProp = 'main';
- var dataChannelStateCbFn = null;
-
- if (self._dataChannels[peerId][transferId]) {
- channelProp = transferId;
- }
-
- // From here we start detecting as completion for data transfer downloads
- self.once('dataTransferState', function () {
- if (dataChannelStateCbFn) {
- self.off('dataChannelState', dataChannelStateCbFn);
- }
-
- delete self._dataTransfers[transferId];
-
- if (self._dataChannels[peerId]) {
- if (channelProp === 'main' && self._dataChannels[peerId].main) {
- self._dataChannels[peerId].main.transferId = null;
- }
-
- if (channelProp === transferId) {
- self._closeDataChannel(peerId, transferId);
- }
- }
- }, function (state, evtTransferId, evtPeerId) {
- return evtTransferId === transferId && evtPeerId === peerId && [
- self.DATA_TRANSFER_STATE.ERROR,
- self.DATA_TRANSFER_STATE.CANCEL,
- self.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED,
- self.DATA_TRANSFER_STATE.USER_REJECTED].indexOf(state) > -1;
- });
-
- if (accept) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'Accepted data transfer and starting ...']);
-
- dataChannelStateCbFn = function (state, evtPeerId, error, cN, cT) {
- log.error([peerId, 'RTCDataChannel', channelProp, 'Data transfer "' + transferId + '" has been terminated due to connection.']);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, peerId,
- self._getTransferInfo(transferId, peerId, true, false, false), {
- transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
- message: new Error('Data transfer terminated as Peer Datachannel connection closed abruptly.')
- });
- };
-
- self.once('dataChannelState', dataChannelStateCbFn, function (state, evtPeerId, error, channelName, channelType) {
- if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- self.off('dataChannelState', dataChannelStateCbFn);
- return;
- }
-
- return evtPeerId === peerId && (channelProp === 'main' ?
- channelType === self.DATA_CHANNEL_STATE.MESSAGING : channelName === transferId) && [
- self.DATA_CHANNEL_STATE.CLOSING,
- self.DATA_CHANNEL_STATE.CLOSED,
- self.DATA_CHANNEL_STATE.ERROR].indexOf(state) > -1;
- });
-
- // Send ACK protocol to start data transfer
- // MCU sends the data transfer from the "P2P" Datachannel
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ACK,
- sender: self._user.sid,
- ackN: 0
- }, channelProp);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOAD_STARTED, transferId, peerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
-
- } else {
- log.warn([peerId, 'RTCDataChannel', transferId, 'Rejected data transfer and data transfer request has been aborted']);
-
- // Send ACK protocol to terminate data transfer request
- // MCU sends the data transfer from the "P2P" Datachannel
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ACK,
- sender: self._user.sid,
- ackN: -1
- }, channelProp);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.USER_REJECTED, transferId, peerId,
- self._getTransferInfo(transferId, peerId, true, false, false), {
- message: new Error('Data transfer terminated as User has rejected data transfer request.'),
- transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD
- });
- }
- };
-
- /**
- * <blockquote class="info">
- * For MCU enabled Peer connections, the cancel data transfer functionality may differ, as it
- * will result in all Peers related to the data transfer ID to be terminated.
- * </blockquote>
- * Function that terminates a currently uploading / downloading data transfer from / to Peer.
- * @method cancelDataTransfer
- * @param {String} peerId The Peer ID.
- * @param {String} transferId The data transfer ID.
- * @example
- * // Example 1: Cancel Peer data transfer
- * var transferSessions = {};
- *
- * skylinkDemo.on("dataTransferState", function (state, transferId, peerId) {
- * if ([skylinkDemo.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
- * skylinkDemo.DATA_TRANSFER_STATE.UPLOAD_STARTED].indexOf(state) > -1) {
- * if (!Array.isArray(transferSessions[transferId])) {
- * transferSessions[transferId] = [];
- * }
- * transferSessions[transferId].push(peerId);
- * } else {
- * transferSessions[transferId].splice(transferSessions[transferId].indexOf(peerId), 1);
- * }
- * });
- *
- * function cancelTransfer (peerId, transferId) {
- * skylinkDemo.cancelDataTransfer(peerId, transferId);
- * }
- * @trigger <small>Event sequence follows <a href="#method_sendBlobData">
- * <code>sendBlobData()</code> method</a> after <code>cancelDataTransfer()</code> method is invoked.</small>
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype.cancelBlobTransfer =
- Skylink.prototype.cancelDataTransfer = function (peerId, transferId) {
- var self = this;
-
- if (!(transferId && typeof transferId === 'string')) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as data transfer ID is not provided']);
- return;
- }
-
- if (!(peerId && typeof peerId === 'string')) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as peer ID is not provided']);
- return;
- }
-
- if (!self._dataTransfers[transferId]) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
- 'data transfer session does not exists.']);
- return;
- }
-
- log.debug([peerId, 'RTCDataChannel', transferId, 'Canceling data transfer ...']);
-
- /**
- * Emit data state event function.
- */
- var emitEventFn = function (peers, transferInfoPeerId) {
- for (var i = 0; i < peers.length; i++) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.CANCEL, transferId, peers[i],
- self._getTransferInfo(transferId, transferInfoPeerId, false, false, false), {
- transferType: self._dataTransfers[transferId].direction,
- message: new Error('User cancelled download transfer')
- });
- }
- };
-
- // For uploading from Peer to MCU case of broadcast
- if (self._hasMCU && self._dataTransfers[transferId].direction === self.DATA_TRANSFER_TYPE.UPLOAD) {
- if (!self._dataChannels.MCU) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
- 'Peer does not have any Datachannel connections']);
- return;
- }
-
- // We abort all data transfers to all Peers if uploading via MCU since it broadcasts to MCU
- log.warn([peerId, 'RTCDataChannel', transferId, 'Aborting all data transfers to Peers']);
-
- // If data transfer to MCU broadcast has interop Peers, send to MCU via the "main" Datachannel
- if (Object.keys(self._dataTransfers[transferId].peers.main).length > 0) {
- self._sendMessageToDataChannel('MCU', {
- type: self._DC_PROTOCOL_TYPE.CANCEL,
- sender: self._user.sid,
- content: 'Peer cancelled download transfer',
- name: self._dataTransfers[transferId].name,
- ackN: 0
- }, 'main');
- }
-
- // If data transfer to MCU broadcast has non-interop Peers, send to MCU via the new Datachanel
- if (Object.keys(self._dataTransfers[transferId].peers[transferId]).length > 0) {
- self._sendMessageToDataChannel('MCU', {
- type: self._DC_PROTOCOL_TYPE.CANCEL,
- sender: self._user.sid,
- content: 'Peer cancelled download transfer',
- name: self._dataTransfers[transferId].name,
- ackN: 0
- }, transferId);
- }
-
- emitEventFn(Object.keys(self._dataTransfers[transferId].peers.main).concat(
- Object.keys(self._dataTransfers[transferId].peers[transferId])));
- } else {
- var channelProp = 'main';
-
- if (!self._dataChannels[peerId]) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Aborting cancel data transfer as ' +
- 'Peer does not have any Datachannel connections']);
- return;
- }
-
- if (self._dataChannels[peerId][transferId]) {
- channelProp = transferId;
- }
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.CANCEL,
- sender: self._user.sid,
- content: 'Peer cancelled download transfer',
- name: self._dataTransfers[transferId].name,
- ackN: 0
- }, channelProp);
-
- emitEventFn([peerId], peerId);
- }
- };
-
- /**
- * Function that sends a message to Peers via the Datachannel connection.
- * <small>Consider using <a href="#method_sendURLData"><code>sendURLData()</code> method</a> if you are
- * sending large strings to Peers.</small>
- * @method sendP2PMessage
- * @param {String|JSON} message The message.
- * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
- * - When provided as an Array, it will send the message to only Peers which IDs are in the list.
- * - When not provided, it will broadcast the message to all connected Peers with Datachannel connection in the Room.
- * @trigger <ol class="desc-seq">
- * <li>Sends P2P message to all targeted Peers. <ol>
- * <li>If Peer connection Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
- * triggering parameter payload <code>state</code> as <code>OPEN</code> and
- * <code>channelType</code> as <code>MESSAGING</code> for Peer.</small> <ol>
- * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>SEND_MESSAGE_ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li>
- * <li><a href="#event_incomingMessage"><code>incomingMessage</code> event</a> triggers
- * parameter payload <code>message.isDataChannel</code> value as <code>true</code> and
- * <code>isSelf</code> value as <code>true</code>.</li></ol></li></ol>
- * @example
- * // Example 1: Broadcasting to all Peers
- * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
- * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
- * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
- * skylinkDemo.sendP2PMessage("Hi all!");
- * }
- * });
- *
- * // Example 2: Sending to specific Peers
- * var peersInExclusiveParty = [];
- *
- * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
- * if (isSelf) return;
- * if (peerInfo.userData.exclusive) {
- * peersInExclusiveParty[peerId] = false;
- * }
- * });
- *
- * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
- * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
- * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
- * peersInExclusiveParty[peerId] = true;
- * }
- * });
- *
- * function updateExclusivePartyStatus (message) {
- * var readyToSend = [];
- * for (var p in peersInExclusiveParty) {
- * if (peersInExclusiveParty.hasOwnProperty(p)) {
- * readyToSend.push(p);
- * }
- * }
- * skylinkDemo.sendP2PMessage(message, readyToSend);
- * }
- * @for Skylink
- * @since 0.5.5
- */
- Skylink.prototype.sendP2PMessage = function(message, targetPeerId) {
- var listOfPeers = Object.keys(this._dataChannels);
- var isPrivate = false;
-
- if (Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- isPrivate = true;
- } else if (targetPeerId && typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- isPrivate = true;
- }
-
- if (!this._inRoom || !(this._user && this._user.sid)) {
- log.error('Unable to send message as User is not in Room. ->', message);
- return;
- }
-
- if (!this._initOptions.enableDataChannel) {
- log.error('Unable to send message as User does not have Datachannel enabled. ->', message);
- return;
- }
-
- // Loop out unwanted Peers
- for (var i = 0; i < listOfPeers.length; i++) {
- var peerId = listOfPeers[i];
-
- if (!this._hasMCU && !this._dataChannels[peerId]) {
- log.error([peerId, 'RTCDataChannel', null, 'Dropping of sending message to Peer as ' +
- 'Datachannel connection does not exists']);
- listOfPeers.splice(i, 1);
- i--;
- } else if (peerId === 'MCU') {
- listOfPeers.splice(i, 1);
- i--;
- } else if (!this._hasMCU) {
- log.debug([peerId, 'RTCDataChannel', null, 'Sending ' + (isPrivate ? 'private' : '') +
- ' P2P message to Peer']);
-
- this._sendMessageToDataChannel(peerId, {
- type: this._DC_PROTOCOL_TYPE.MESSAGE,
- isPrivate: isPrivate,
- sender: this._user.sid,
- target: peerId,
- data: message
- }, 'main');
- }
- }
-
- if (listOfPeers.length === 0) {
- log.warn('Currently there are no Peers to send P2P message to (unless the message is queued ' +
- 'and there are Peer connected by then).');
- }
-
- if (this._hasMCU) {
- log.debug(['MCU', 'RTCDataChannel', null, 'Broadcasting ' + (isPrivate ? 'private' : '') +
- ' P2P message to Peers']);
-
- this._sendMessageToDataChannel('MCU', {
- type: this._DC_PROTOCOL_TYPE.MESSAGE,
- isPrivate: isPrivate,
- sender: this._user.sid,
- target: listOfPeers,
- data: message
- }, 'main');
- }
-
- this._trigger('incomingMessage', {
- content: message,
- isPrivate: isPrivate,
- targetPeerId: targetPeerId || null,
- listOfPeers: listOfPeers,
- isDataChannel: true,
- senderPeerId: this._user.sid
- }, this._user.sid, this.getPeerInfo(), true);
- };
-
- /**
- * <blockquote class="info">
- * Note that this feature is not supported by MCU enabled Peer connections and the
- * <code>enableSimultaneousTransfers</code> flag has to be enabled in the <a href="#method_init">
- * <code>init()</code> method</a> in order for this functionality to work.<br>
- * To start streaming data, see the <a href="#method_streamData"><code>streamData()</code>
- * method</a>. To stop data streaming session, see the <a href="#method_stopStreamingData"><code>
- * stopStreamingData()</code> method</a>.
- * </blockquote>
- * Function that starts a data chunks streaming session from User to Peers.
- * @method startStreamingData
- * @param {Boolean} [isStringStream=false] The flag if data streaming session sending data chunks
- * should be expected as string data chunks sent.
- * <small>By default, data chunks are expected to be sent in Blob or ArrayBuffer, and ArrayBuffer
- * data chunks will be converted to Blob.</small>
- * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
- * - When provided as an Array, it will start streaming session to only Peers which IDs are in the list.
- * - When not provided, it will start the streaming session to all connected Peers with Datachannel connection in the Room.
- * @trigger <ol class="desc-seq">
- * @trigger <ol class="desc-seq">
- * <li>Checks if User is in Room. <ol>
- * <li>If User is not in Room: <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if there is any available Datachannel connections. <ol>
- * <li>If User is not in Room: <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
- * <li>If Peer connection or session does not exists: <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code>
- * as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If Peer connection messaging Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
- * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>method</a> and connected: <ol>
- * <li>If MCU Peer connection messaging Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code>, <code>peerId</code> value as <code>"MCU"</code>
- * and <code>channelType</code> as <code>MESSAGING</code> for MCU Peer.</small>
- * <ol><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>START_ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Checks if should open a new data Datachannel.<ol>
- * <li>If Peer supports simultaneous data streaming, open new data Datachannel: <small>If MCU is connected,
- * this opens a new data Datachannel with MCU Peer with all the Peers IDs information that supports
- * simultaneous data transfers targeted for the data streaming session instead of opening new data Datachannel
- * with all Peers targeted for the data streaming session.</small> <ol>
- * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter
- * payload <code>state</code> as <code>CONNECTING</code> and <code>channelType</code> as <code>DATA</code>.
- * <small>Note that there is no timeout to wait for parameter payload <code>state</code> to be
- * <code>OPEN</code>.</small></li>
- * <li>If Datachannel has been created and opened successfully: <ol>
- * <li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggers parameter payload
- * <code>state</code> as <code>OPEN</code> and <code>channelType</code> as <code>DATA</code>.</li></ol></li>
- * <li>Else: <ol><li><a href="#event_dataChannelState"><code>dataChannelState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>CREATE_ERROR</code> and <code>channelType</code> as
- * <code>DATA</code>.</li><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a> triggers
- * parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li></ol></li><li>Else: <small>If MCU is connected,
- * this uses the messaging Datachannel with MCU Peer with all the Peers IDs information that supports
- * simultaneous data transfers targeted for the data streaming session instead of using the messaging Datachannels
- * with all Peers targeted for the data streaming session.</small> <ol><li>If messaging Datachannel connection has a
- * data streaming in-progress: <ol><li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li><li>If there is any conflicting <a href="#method_streamData"><code>streamData()</code>
- * method</a> data streaming session: <small>If <code>isStringStream</code> is provided as <code>true</code> and
- * <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a> or <a href="#method_sendURLData">
- * <code>sendURLData()</code> method</a> has an existing binary string transfer, it cannot start string data
- * streaming session. Else if <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a>
- * has an existing binary data transfer, it cannot start binary data streaming session.</small><ol>
- * <li><a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>START_ERROR</code>.</li><li><b>ABORT</b> step and
- * return error.</li></ol></li></li></ol></ol></li></ol></li>
- * <li>Starts the data streaming session with Peer. <ol>
- * <li><a href="#event_incomingDataStreamStarted"><code>incomingDataStreamStarted</code> event</a> triggers.</li>
- * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>SENDING_STARTED</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>RECEIVING_STARTED</code>.</li></ol></li></ol>
- * @example
- * // Example 1: Start streaming to all Peers
- * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
- * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
- * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
- * skylinkDemo.startStreamingData(false);
- * }
- * });
- *
- * // Example 2: Start streaming to specific Peers
- * var peersInExclusiveParty = [];
- *
- * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
- * if (isSelf) return;
- * if (peerInfo.userData.exclusive) {
- * peersInExclusiveParty[peerId] = false;
- * }
- * });
- *
- * skylinkDemo.on("dataChannelState", function (state, peerId, error, channelName, channelType) {
- * if (state === skylinkDemo.DATA_CHANNEL_STATE.OPEN &&
- * channelType === skylinkDemo.DATA_CHANNEL_TYPE.MESSAGING) {
- * peersInExclusiveParty[peerId] = true;
- * }
- * });
- *
- * function updateExclusivePartyStatus (message) {
- * var readyToSend = [];
- * for (var p in peersInExclusiveParty) {
- * if (peersInExclusiveParty.hasOwnProperty(p)) {
- * readyToSend.push(p);
- * }
- * }
- * skylinkDemo.startStreamingData(message, readyToSend);
- * }
- * @beta
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype.startStreamingData = function(isStringStream, targetPeerId) {
- var self = this;
- var listOfPeers = Object.keys(self._dataChannels);
- var isPrivate = false;
- var sessionChunkType = 'binary';
-
- if (Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- isPrivate = true;
- } else if (targetPeerId && typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- isPrivate = true;
- }
-
- if (Array.isArray(isStringStream)) {
- listOfPeers = isStringStream;
- targetPeerId = isStringStream;
- isPrivate = true;
- } else if (isStringStream && typeof isStringStream === 'string') {
- listOfPeers = [isStringStream];
- targetPeerId = isStringStream;
- isPrivate = true;
- } else if (isStringStream && typeof isStringStream === 'boolean') {
- sessionChunkType = 'string';
- }
-
- var sessionInfo = {
- chunk: null,
- chunkSize: 0,
- chunkType: sessionChunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
- isPrivate: isPrivate,
- isStringStream: sessionChunkType === 'string',
- senderPeerId: self._user && self._user.sid ? self._user.sid : null
- };
-
- // Remove MCU from list
- if (listOfPeers.indexOf('MCU') > -1) {
- listOfPeers.splice(listOfPeers.indexOf('MCU'), 1);
- }
-
- var emitErrorBeforeStreamingFn = function (error) {
- log.error(error);
-
- /*if (listOfPeers.length > 0) {
- for (var i = 0; i < listOfPeers.length; i++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, null,
- listOfPeers[i], sessionInfo, new Error(error));
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, null,
- null, sessionInfo, new Error(error));
- }*/
- };
-
- if (!this._inRoom || !(this._user && this._user.sid)) {
- return emitErrorBeforeStreamingFn('Unable to start data streaming as User is not in Room.');
- }
-
- if (!this._initOptions.enableDataChannel) {
- return emitErrorBeforeStreamingFn('Unable to start data streaming as User does not have Datachannel enabled.');
- }
-
- if (listOfPeers.length === 0) {
- return emitErrorBeforeStreamingFn('Unable to start data streaming as there are no Peers to start session with.');
- }
-
- if (self._hasMCU) {
- return emitErrorBeforeStreamingFn('Unable to start data streaming as this feature is current not supported by MCU yet.');
- }
-
- if (!self._initOptions.enableSimultaneousTransfers) {
- return emitErrorBeforeStreamingFn('Unable to start data streaming as this feature requires simultaneous data transfers to be enabled');
- }
-
- var transferId = 'stream_' + (self._user && self._user.sid ? self._user.sid : '-') + '_' + (new Date()).getTime();
- var peersInterop = [];
- var peersNonInterop = [];
- var sessions = {};
- var listenToPeerFn = function (peerId, channelProp) {
- var hasStarted = false;
- sessions[peerId] = channelProp;
-
- self.once('dataStreamState', function () {}, function (state, evtTransferId, evtPeerId, evtSessionInfo) {
- if (!(evtTransferId === transferId && evtPeerId === peerId)) {
- return;
- }
-
- var dataChunk = evtSessionInfo.chunk;
- var updatedSessionInfo = clone(evtSessionInfo);
- delete updatedSessionInfo.chunk;
-
- if (state === self.DATA_STREAM_STATE.SENDING_STARTED) {
- hasStarted = true;
- return;
- }
-
- if (hasStarted && [self.DATA_STREAM_STATE.ERROR, self.DATA_STREAM_STATE.SENDING_STOPPED].indexOf(state) > -1) {
- if (channelProp === transferId) {
- self._closeDataChannel(peerId, transferId);
- }
-
- if (self._dataStreams[transferId] && self._dataStreams[transferId].sessions[peerId]) {
- delete self._dataStreams[transferId].sessions[peerId];
-
- if (Object.keys(self._dataStreams[transferId].sessions).length === 0) {
- delete self._dataStreams[transferId];
- }
- }
- return true;
- }
- });
- };
-
- // Loop out unwanted Peers
- for (var i = 0; i < listOfPeers.length; i++) {
- var peerId = listOfPeers[i];
- var error = null;
- var dtProtocolVersion = ((self._peerInformations[peerId] || {}).agent || {}).DTProtocolVersion || '';
- var channelProp = self._isLowerThanVersion(dtProtocolVersion, '0.1.2') || !self._initOptions.enableSimultaneousTransfers ? 'main' : transferId;
-
- if (!(self._dataChannels[peerId] && self._dataChannels[peerId].main)) {
- error = 'Datachannel connection does not exists';
- } else if (self._hasMCU && !(self._dataChannels.MCU && self._dataChannels.MCU.main)) {
- error = 'MCU Datachannel connection does not exists';
- } else if (self._isLowerThanVersion(dtProtocolVersion, '0.1.3')) {
- error = 'Peer DTProtocolVersion does not support data streaming. (received: "' + dtProtocolVersion + '", expected: "0.1.3")';
- } else {
- if (channelProp === 'main') {
- var dataTransferId = self._hasMCU ? self._dataChannels.MCU.main.transferId : self._dataChannels[peerId].main.transferId;
-
- if (self._dataChannels[peerId].main.streamId) {
- error = 'Peer Datachannel currently has an active data transfer session.';
- } else if (self._hasMCU && self._dataChannels.MCU.main.streamId) {
- error = 'MCU Peer Datachannel currently has an active data transfer session.';
- } else if (self._dataTransfers[dataTransferId] && self._dataTransfers[dataTransferId].sessionChunkType === sessionChunkType) {
- error = (self._hasMCU ? 'MCU ' : '') + 'Peer Datachannel currently has an active ' + sessionChunkType + ' data transfer.';
- } else {
- peersInterop.push(peerId);
- }
- } else {
- peersNonInterop.push(peerId);
- }
- }
-
- if (error) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, new Error(error));
- listOfPeers.splice(i, 1);
- i--;
- } else {
- listenToPeerFn(peerId, channelProp);
- }
- }
-
- if (listOfPeers.length === 0) {
- log.warn('There are no Peers to start data session with.');
- return;
- }
-
- self._dataStreams[transferId] = {
- sessions: sessions,
- chunkType: sessionChunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
- sessionChunkType: sessionChunkType,
- isPrivate: isPrivate,
- isStringStream: sessionChunkType === 'string',
- senderPeerId: self._user && self._user.sid ? self._user.sid : null,
- isUpload: true
- };
-
- var startDataSessionFn = function (peerId, channelProp, targetPeers) {
- self.once('dataChannelState', function () {}, function (state, evtPeerId, channelName, channelType, error) {
- if (!self._dataStreams[transferId]) {
- return true;
- }
-
- if (!(evtPeerId === peerId && (channelProp === 'main' ? channelType === self.DATA_CHANNEL_TYPE.MESSAGING :
- channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA))) {
- return;
- }
-
- if ([self.DATA_CHANNEL_STATE.ERROR, self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1) {
- var updatedError = new Error(error && error.message ? error.message :
- 'Failed data transfer as datachannel state is "' + state + '".');
-
- if (peerId === 'MCU') {
- for (var mp = 0; mp < targetPeers.length; mp++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, targetPeers[mp], sessionInfo, updatedError);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo, updatedError);
- }
- return true;
- }
- });
-
- if (!(self._dataChannels[peerId][channelProp] &&
- self._dataChannels[peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN)) {
- var notOpenError = new Error('Failed starting data streaming session as channel is not opened.');
- if (peerId === 'MCU') {
- for (i = 0; i < targetPeers.length; i++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, targetPeers[i], sessionInfo, notOpenError);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, notOpenError);
- }
- }
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.WRQ,
- transferId: transferId,
- name: transferId,
- size: 0,
- originalSize: 0,
- dataType: 'fastBinaryStart',
- mimeType: null,
- chunkType: sessionChunkType,
- chunkSize: 0,
- timeout: 0,
- isPrivate: isPrivate,
- sender: self._user.sid,
- agent: AdapterJS.webrtcDetectedBrowser,
- version: AdapterJS.webrtcDetectedVersion,
- target: peerId === 'MCU' ? targetPeers : peerId
- }, channelProp);
- self._dataChannels[peerId][channelProp].streamId = transferId;
-
- var updatedSessionInfo = clone(sessionInfo);
- delete updatedSessionInfo.chunk;
-
- if (peerId === 'MCU') {
- for (var tp = 0; tp < targetPeers.length; tp++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STARTED, transferId, targetPeers[tp], sessionInfo, null);
- self._trigger('incomingDataStreamStarted', transferId, targetPeers[tp], updatedSessionInfo, true);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STARTED, transferId, peerId, sessionInfo, null);
- self._trigger('incomingDataStreamStarted', transferId, peerId, updatedSessionInfo, true);
- }
- };
-
- var waitForChannelOpenFn = function (peerId, targetPeers) {
- self.once('dataChannelState', function (state, evtPeerId, error) {
- if (state === self.DATA_CHANNEL_STATE.CREATE_ERROR) {
- if (peerId === 'MCU') {
- for (var mp = 0; mp < targetPeers.length; mp++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, targetPeers[mp], sessionInfo, error);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.START_ERROR, transferId, peerId, sessionInfo, error);
- }
- return;
- }
- startDataSessionFn(peerId, transferId, targetPeers);
- }, function (state, evtPeerId, error, channelName, channelType) {
- if (evtPeerId === peerId && channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA) {
- return [self.DATA_CHANNEL_STATE.CREATE_ERROR, self.DATA_CHANNEL_STATE.OPEN].indexOf(state) > -1;
- }
- });
- self._createDataChannel(peerId, transferId, sessionChunkType === 'string' ? self._CHUNK_DATAURL_SIZE :
- (AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE));
- };
-
- if (peersNonInterop.length > 0) {
- if (self._hasMCU) {
- waitForChannelOpenFn('MCU', peersNonInterop);
- } else {
- for (var pni = 0; pni < peersNonInterop.length; pni++) {
- waitForChannelOpenFn(peersNonInterop[pni], null);
- }
- }
- }
-
- if (peersInterop.length > 0) {
- if (self._hasMCU) {
- startDataSessionFn('MCU', 'main', peersInterop);
- } else {
- for (var pi = 0; pi < peersInterop.length; pi++) {
- startDataSessionFn(peersInterop[pi], 'main', null);
- }
- }
- }
- };
-
- /**
- * <blockquote class="info">
- * Note that this feature is not supported by MCU enabled Peer connections.<br>
- * To start data streaming session, see the <a href="#method_startStreamingData"><code>startStreamingData()</code>
- * method</a>. To stop data streaming session, see the <a href="#method_stopStreamingData"><code>stopStreamingData()</code> method</a>
- * </blockquote>
- * Function that sends a data chunk from User to Peers for an existing active data streaming session.
- * @method streamData
- * @param {String} streamId The data streaming session ID.
- * @param {String|Blob|ArrayBuffer} chunk The data chunk.
- * <small>By default when it is not string data streaming, data chunks when is are expected to be
- * sent in Blob or ArrayBuffer, and ArrayBuffer data chunks will be converted to Blob.</small>
- * <small>For binary data chunks, the limit is <code>65456</code>.</small>
- * <small>For string data chunks, the limit is <code>1212</code>.</small>
- * @trigger <ol class="desc-seq">
- * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
- * <li>If Peer connection (or MCU Peer connection if enabled)
- * data streaming Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
- * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Starts sending the data chunk to Peer. <ol>
- * <li><a href="#event_incomingDataStream"><code>incomingDataStream</code> event</a> triggers.</li>
- * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>SENT</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>RECEIVED</code>.</li></ol></li></ol>
- * @example
- * // Example 1: Start streaming
- * var currentStreamId = null
- * if (file.size > chunkLimit) {
- * while ((file.size - 1) > endCount) {
- * endCount = startCount + chunkLimit;
- * chunks.push(file.slice(startCount, endCount));
- * startCount += chunkLimit;
- * }
- * if ((file.size - (startCount + 1)) > 0) {
- * chunks.push(file.slice(startCount, file.size - 1));
- * }
- * } else {
- * chunks.push(file);
- * }
- * var processNextFn = function () {
- * if (chunks.length > 0) {
- * skylinkDemo.once("incomingDataStream", function () {
- * setTimeout(processNextFn, 1);
- * }, function (data, evtStreamId, evtPeerId, streamInfo, isSelf) {
- * return isSelf && evtStreamId === currentStreamId;
- * });
- * var chunk = chunks[0];
- * chunks.splice(0, 1);
- * skylinkDemo.streamData(currentStreamId, chunk);
- * } else {
- * skylinkDemo.stopStreamingData(currentStreamId);
- * }
- * };
- * skylinkDemo.once("incomingDataStreamStarted", processNextFn, function (streamId, peerId, streamInfo, isSelf) {
- * currentStreamId = streamId;
- * return isSelf;
- * });
- * skylinkDemo.once("incomingDataStreamStopped", function () {
- * // Render file
- * }, function (streamId, peerId, streamInfo, isSelf) {
- * return currentStreamId === streamId && isSelf;
- * });
- * skylinkDemo.startStreamingData(false);
- * @beta
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype.streamData = function(transferId, dataChunk) {
- var self = this;
-
- if (!(transferId && typeof transferId === 'string')) {
- log.error('Failed streaming data chunk as stream session ID is not provided.');
- return;
- }
-
- if (!(dataChunk && typeof dataChunk === 'object' && (dataChunk instanceof Blob || dataChunk instanceof ArrayBuffer))) {
- log.error('Failed streaming data chunk as it is not provided.');
- return;
- }
-
- if (!(self._inRoom && self._user && self._user.sid)) {
- log.error('Failed streaming data chunk as User is not in the Room.');
- return;
- }
-
- if (!self._dataStreams[transferId]) {
- log.error('Failed streaming data chunk as session does not exists.');
- return;
- }
-
- if (!self._dataStreams[transferId].isUpload) {
- log.error('Failed streaming data chunk as session is not sending.');
- return;
- }
-
- if (self._dataStreams[transferId].sessionChunkType === 'string' ? typeof dataChunk !== 'string' :
- typeof dataChunk !== 'object') {
- log.error('Failed streaming data chunk as data chunk does not match expected data type.');
- return;
- }
-
- if (self._hasMCU) {
- log.error('Failed streaming data chunk as MCU does not support this feature yet.');
- return;
- }
-
- var updatedDataChunk = dataChunk instanceof ArrayBuffer ? new Blob(dataChunk) : dataChunk;
-
- if (self._dataStreams[transferId].sessionChunkType === 'string' ? updatedDataChunk.length > self._CHUNK_DATAURL_SIZE :
- updatedDataChunk.length > self._BINARY_FILE_SIZE) {
- log.error('Failed streaming data chunk as data chunk exceeds maximum chunk limit.');
- return;
- }
-
- var sessionInfo = {
- chunk: updatedDataChunk,
- chunkSize: updatedDataChunk.size || updatedDataChunk.length || updatedDataChunk.byteLength,
- chunkType: self._dataStreams[transferId].sessionChunkType === 'string' ?
- self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
- isPrivate: self._dataStreams[transferId].sessionChunkType.isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: self._user && self._user.sid ? self._user.sid : null
- };
-
- var peersInterop = [];
- var peersNonInterop = [];
- var sendDataFn = function (peerId, channelProp, targetPeers) {
- // When ready to be sent
- var onSendDataFn = function (buffer) {
- self._sendMessageToDataChannel(peerId, buffer, channelProp, true);
-
- var updatedSessionInfo = clone(sessionInfo);
- delete updatedSessionInfo.chunk;
-
- if (targetPeers) {
- for (var i = 0; i < targetPeers.length; i++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENT, transferId, targetPeers[i], sessionInfo, null);
- self._trigger('incomingDataStream', dataChunk, transferId, targetPeers[i], updatedSessionInfo, true);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENT, transferId, peerId, sessionInfo, null);
- self._trigger('incomingDataStream', dataChunk, transferId, peerId, updatedSessionInfo, true);
- }
- };
-
- if (dataChunk instanceof Blob && sessionInfo.chunkType === self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
- self._blobToArrayBuffer(dataChunk, onSendDataFn);
- } else if (!(dataChunk instanceof Blob) && sessionInfo.chunkType === self.DATA_TRANSFER_DATA_TYPE.BLOB) {
- onSendDataFn(new Blob([dataChunk]));
- } else if (AdapterJS.webrtcDetectedType === 'plugin' && typeof dataChunk !== 'string') {
- onSendDataFn(new Int8Array(dataChunk));
- } else {
- onSendDataFn(dataChunk);
- }
- };
-
- for (var peerId in self._dataStreams[transferId].sessions) {
- if (self._dataStreams[transferId].sessions.hasOwnProperty(peerId) && self._dataStreams[transferId].sessions[peerId]) {
- var channelProp = self._dataStreams[transferId].sessions[peerId];
-
- if (!(self._dataChannels[self._hasMCU ? 'MCU' : peerId] && self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp] &&
- self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN &&
- self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].streamId === transferId)) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Failed streaming data as it has not started or is ready.']);
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo,
- new Error('Streaming as it has not started or Datachannel connection is not open.'));
- return;
- }
-
- if (self._hasMCU) {
- if (channelProp === 'main') {
- peersInterop.push(peerId);
- } else {
- peersNonInterop.push(peerId);
- }
- } else {
- sendDataFn(peerId, channelProp);
- }
- }
- }
-
- if (self._hasMCU) {
- if (peersInterop.length > 0) {
- sendDataFn(peerId, 'main', peersInterop);
- }
- if (peersNonInterop.length > 0) {
- sendDataFn(peerId, transferId, peersNonInterop);
- }
- }
- };
-
- /**
- * <blockquote class="info">
- * To start data streaming session, see the <a href="#method_startStreamingData"><code>startStreamingData()</code>
- * method</a> To start streaming data, see the <a href="#method_streamData"><code>streamData()</code>
- * method</a>.
- * </blockquote>
- * Function that stops a data chunks streaming session from User to Peers.
- * @method stopStreamingData
- * @param {String} streamId The data streaming session ID.
- * @trigger <ol class="desc-seq">
- * <li>Checks if Peer connection and Datachannel connection are in correct states. <ol>
- * <li>If Peer connection (or MCU Peer connection if enabled)
- * data streaming Datachannel has not been opened: <small>This can be checked with
- * <a href="#event_dataChannelState"><code>dataChannelState</code> event</a> triggering parameter
- * payload <code>state</code> as <code>OPEN</code> and <code>channelType</code> as
- * <code>MESSAGING</code> for Peer.</small> <ol><li><a href="#event_dataStreamState">
- * <code>dataStreamState</code> event</a> triggers parameter payload <code>state</code> as <code>ERROR</code>.</li>
- * <li><b>ABORT</b> step and return error.</li></ol></li></ol></li>
- * <li>Stops the data streaming session to Peer. <ol>
- * <li><a href="#event_incomingDataStreamStopped"><code>incomingDataStreamStopped</code> event</a> triggers.</li>
- * <li><em>For User only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>SENDING_STOPPED</code>.</li>
- * <li><em>For Peer only</em> <a href="#event_dataStreamState"><code>dataStreamState</code> event</a>
- * triggers parameter payload <code>state</code> as <code>RECEIVING_STOPPED</code>.</li></ol></li></ol>
- * @example
- * skylinkDemo.stopStreamData(streamId);
- * @beta
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype.stopStreamingData = function(transferId) {
- var self = this;
-
- if (!(transferId && typeof transferId === 'string')) {
- log.error('Failed streaming data chunk as stream session ID is not provided.');
- return;
- }
-
- if (!(self._inRoom && self._user && self._user.sid)) {
- log.error('Failed streaming data chunk as User is not in the Room.');
- return;
- }
-
- if (!self._dataStreams[transferId]) {
- log.error('Failed stopping data streaming session as it does not exists.');
- return;
- }
-
- if (!self._dataStreams[transferId].isUpload) {
- log.error('Failed stopping data streaming session as it is not sending.');
- return;
- }
-
- if (self._hasMCU) {
- log.error('Failed stopping data streaming session as MCU does not support this feature yet.');
- return;
- }
-
- var sessionInfo = {
- chunk: null,
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].sessionChunkType === 'string' ?
- self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
- isPrivate: self._dataStreams[transferId].sessionChunkType.isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: self._user && self._user.sid ? self._user.sid : null
- };
-
- var peersInterop = [];
- var peersNonInterop = [];
- var sendDataFn = function (peerId, channelProp, targetPeers) {
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.WRQ,
- transferId: transferId,
- name: transferId,
- size: 0,
- originalSize: 0,
- dataType: 'fastBinaryStop',
- mimeType: null,
- chunkType: self._dataStreams[transferId].sessionChunkType,
- chunkSize: 0,
- timeout: 0,
- isPrivate: self._dataStreams[transferId].isPrivate,
- sender: self._user.sid,
- agent: AdapterJS.webrtcDetectedBrowser,
- version: AdapterJS.webrtcDetectedVersion,
- target: targetPeers ? targetPeers : peerId
- }, channelProp);
-
- var updatedSessionInfo = clone(sessionInfo);
- delete updatedSessionInfo.chunk;
-
- if (targetPeers) {
- for (var i = 0; i < targetPeers.length; i++) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STOPPED, transferId, targetPeers[i], sessionInfo, null);
- self._trigger('incomingDataStreamStopped', transferId, targetPeers[i], updatedSessionInfo, true);
- }
- } else {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.SENDING_STOPPED, transferId, peerId, sessionInfo, null);
- self._trigger('incomingDataStreamStopped', transferId, peerId, updatedSessionInfo, true);
- }
- };
-
- for (var peerId in self._dataStreams[transferId].sessions) {
- if (self._dataStreams[transferId].sessions.hasOwnProperty(peerId) && self._dataStreams[transferId].sessions[peerId]) {
- var channelProp = self._dataStreams[transferId].sessions[peerId];
-
- if (!(self._dataChannels[self._hasMCU ? 'MCU' : peerId] && self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp] &&
- self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].channel.readyState === self.DATA_CHANNEL_STATE.OPEN &&
- self._dataChannels[self._hasMCU ? 'MCU' : peerId][channelProp].streamId === transferId)) {
- log.error([peerId, 'RTCDataChannel', transferId, 'Failed stopping data streaming session as channel is closed.']);
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, peerId, sessionInfo,
- new Error('Failed stopping data streaming session as Datachannel connection is not open or is active.'));
- return;
- }
-
- if (self._hasMCU) {
- if (self._dataStreams[transferId].sessions[peerId] === 'main') {
- peersInterop.push(peerId);
- } else {
- peersNonInterop.push(peerId);
- }
- } else {
- sendDataFn(peerId, channelProp);
- }
- }
- }
-
- if (self._hasMCU) {
- if (peersInterop.length > 0) {
- sendDataFn(peerId, 'main', peersInterop);
- }
- if (peersNonInterop.length > 0) {
- sendDataFn(peerId, transferId, peersNonInterop);
- }
- }
- };
-
-
- /**
- * Function that starts the data transfer to Peers.
- * @method _startDataTransfer
- * @private
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype._startDataTransfer = function(data, timeout, targetPeerId, sendChunksAsBinary, callback, sessionType) {
- var self = this;
- var transferId = (self._user ? self._user.sid : '') + '_' + (new Date()).getTime();
- var transferErrors = {};
- var transferCompleted = [];
- var chunks = [];
- var listOfPeers = Object.keys(self._peerConnections);
- var sessionChunkType = 'string';
- var transferInfo = {
- name: null,
- size: null,
- chunkSize: null,
- chunkType: null,
- dataType: null,
- mimeType: null,
- direction: self.DATA_TRANSFER_TYPE.UPLOAD,
- timeout: 60,
- isPrivate: false,
- percentage: 0
- };
-
- // sendBlobData(.., timeout)
- if (typeof timeout === 'number') {
- transferInfo.timeout = timeout;
- } else if (Array.isArray(timeout)) {
- listOfPeers = timeout;
- } else if (timeout && typeof timeout === 'string') {
- listOfPeers = [timeout];
- } else if (timeout && typeof timeout === 'boolean') {
- sessionChunkType = 'binary';
- } else if (typeof timeout === 'function') {
- callback = timeout;
- }
-
- // sendBlobData(.., .., targetPeerId)
- if (Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- } else if (targetPeerId && typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- } else if (targetPeerId && typeof targetPeerId === 'boolean') {
- sessionChunkType = 'binary';
- } else if (typeof targetPeerId === 'function') {
- callback = targetPeerId;
- }
-
- // sendBlobData(.., .., .., sendChunksAsBinary)
- if (sendChunksAsBinary && typeof sendChunksAsBinary === 'boolean') {
- sessionChunkType = 'binary';
- } else if (typeof sendChunksAsBinary === 'function') {
- callback = sendChunksAsBinary;
- }
-
- // Remove MCU Peer as list of Peers
- // if (listOfPeers.indexOf('MCU') > -1) {
- // listOfPeers.splice(listOfPeers.indexOf('MCU'), 1);
- // }
-
- // Function that returns the error emitted before data transfer has started
- var emitErrorBeforeDataTransferFn = function (error) {
- log.error(error);
-
- if (typeof callback === 'function') {
- var transferErrors = {};
-
- if (listOfPeers.length === 0) {
- transferErrors.self = new Error(error);
- /*self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.START_ERROR, null, null, transferInfo, {
- transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
- message: new Error(error)
- });*/
- } else {
- for (var i = 0; i < listOfPeers.length; i++) {
- transferErrors[listOfPeers[i]] = new Error(error);
- /*self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.START_ERROR, null, listOfPeers[i], transferInfo, {
- transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
- message: new Error(error)
- });*/
- }
- }
-
- callback({
- transferId: null,
- transferInfo: transferInfo,
- listOfPeers: listOfPeers,
- transferErrors: transferErrors
- }, null);
- }
- };
-
- if (sessionType === 'blob') {
- if (self._hasMCU && sessionChunkType === 'binary') {
- log.warn('Binary data chunks transfer is not yet supported with MCU environment. ' +
- 'Fallbacking to binary string data chunks transfer.');
- sessionChunkType = 'string';
- }
-
- var chunkSize = sessionChunkType === 'string' ? (AdapterJS.webrtcDetectedBrowser === 'firefox' ?
- self._MOZ_CHUNK_FILE_SIZE : self._CHUNK_FILE_SIZE) : (AdapterJS.webrtcDetectedBrowser === 'firefox' ?
- self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE);
-
- transferInfo.dataType = self.DATA_TRANSFER_SESSION_TYPE.BLOB;
- transferInfo.chunkSize = sessionChunkType === 'string' ? 4 * Math.ceil(chunkSize / 3) : chunkSize;
- transferInfo.chunkType = sessionChunkType === 'binary' ? self._binaryChunkType : self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING;
-
- // Start checking if data transfer can start
- if (!(data && typeof data === 'object' && data instanceof Blob)) {
- emitErrorBeforeDataTransferFn('Provided data is not a Blob data');
- return;
- }
-
- transferInfo.name = data.name || transferId;
- transferInfo.mimeType = data.type || null;
-
- if (data.size < 1) {
- emitErrorBeforeDataTransferFn('Provided data is not a valid Blob data.');
- return;
- }
-
- transferInfo.originalSize = data.size;
- transferInfo.size = sessionChunkType === 'string' ? 4 * Math.ceil(data.size / 3) : data.size;
- chunks = self._chunkBlobData(data, chunkSize);
- } else {
- transferInfo.dataType = self.DATA_TRANSFER_SESSION_TYPE.DATA_URL;
- transferInfo.chunkSize = self._CHUNK_DATAURL_SIZE;
- transferInfo.chunkType = self.DATA_TRANSFER_DATA_TYPE.STRING;
-
- // Start checking if data transfer can start
- if (!(data && typeof data === 'string')) {
- emitErrorBeforeDataTransferFn('Provided data is not a dataURL');
- return;
- }
-
- transferInfo.originalSize = transferInfo.size = data.length || data.size;
- chunks = self._chunkDataURL(data, transferInfo.chunkSize);
- }
-
- if (!(self._user && self._user.sid)) {
- emitErrorBeforeDataTransferFn('Unable to send any ' +
- sessionType.replace('data', 'dataURL') + ' data. User is not in Room.');
- return;
- }
-
- if (!self._initOptions.enableDataChannel) {
- emitErrorBeforeDataTransferFn('Unable to send any ' +
- sessionType.replace('data', 'dataURL') + ' data. Datachannel is disabled');
- return;
- }
-
- if (listOfPeers.length === 0) {
- emitErrorBeforeDataTransferFn('Unable to send any ' +
- sessionType.replace('data', 'dataURL') + ' data. There are no Peers to start data transfer with');
- return;
- }
-
- self._dataTransfers[transferId] = clone(transferInfo);
- self._dataTransfers[transferId].peers = {};
- self._dataTransfers[transferId].peers.main = {};
- self._dataTransfers[transferId].peers[transferId] = {};
- self._dataTransfers[transferId].sessions = {};
- self._dataTransfers[transferId].chunks = chunks;
- self._dataTransfers[transferId].enforceBSPeers = [];
- self._dataTransfers[transferId].enforcedBSInfo = {};
- self._dataTransfers[transferId].sessionType = sessionType;
- self._dataTransfers[transferId].sessionChunkType = sessionChunkType;
- self._dataTransfers[transferId].senderPeerId = self._user.sid;
-
- // Check if fallback chunks is required
- if (sessionType === 'blob' && sessionChunkType === 'binary') {
- for (var p = 0; p < listOfPeers.length; p++) {
- var protocolVer = (((self._peerInformations[listOfPeers[p]]) || {}).agent || {}).DTProtocolVersion || '0.1.0';
-
- // C++ SDK does not support binary file transfer for now
- if (self._isLowerThanVersion(protocolVer, '0.1.3')) {
- self._dataTransfers[transferId].enforceBSPeers.push(listOfPeers[p]);
- }
- }
-
- if (self._dataTransfers[transferId].enforceBSPeers.length > 0) {
- var bsChunkSize = AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_CHUNK_FILE_SIZE : self._CHUNK_FILE_SIZE;
- var bsChunks = self._chunkBlobData(new Blob(chunks), bsChunkSize);
-
- self._dataTransfers[transferId].enforceBSInfo = {
- chunkSize: 4 * Math.ceil(bsChunkSize / 3),
- chunkType: self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
- size: 4 * Math.ceil(transferInfo.originalSize / 3),
- chunks: bsChunks
- };
- }
- }
-
- /**
- * Complete Peer function.
- */
- var completeFn = function (peerId, error) {
- // Ignore if already added.
- if (transferCompleted.indexOf(peerId) > -1) {
- return;
- }
-
- log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer result. Is errors present? ->'], error);
-
- transferCompleted.push(peerId);
-
- if (error) {
- transferErrors[peerId] = new Error(error);
- }
-
- if (listOfPeers.length === transferCompleted.length) {
- log.log([null, 'RTCDataChannel', transferId, 'Data transfer request completed']);
-
- if (typeof callback === 'function') {
- if (Object.keys(transferErrors).length > 0) {
- callback({
- transferId: transferId,
- transferInfo: self._getTransferInfo(transferId, peerId, false, true, false),
- transferErrors: transferErrors,
- listOfPeers: listOfPeers
- }, null);
- } else {
- callback(null, {
- transferId: transferId,
- transferInfo: self._getTransferInfo(transferId, peerId, false, true, false),
- listOfPeers: listOfPeers
- });
- }
- }
- }
- };
-
- for (var i = 0; i < listOfPeers.length; i++) {
- var MCUInteropStatus = self._startDataTransferToPeer(transferId, listOfPeers[i], completeFn, null, null);
-
- if (typeof MCUInteropStatus === 'boolean') {
- if (MCUInteropStatus === true) {
- self._dataTransfers[transferId].peers.main[listOfPeers[i]] = true;
- } else {
- self._dataTransfers[transferId].peers[transferId][listOfPeers[i]] = true;
- }
- }
- }
-
- if (self._hasMCU) {
- if (Object.keys(self._dataTransfers[transferId].peers.main).length > 0) {
- self._startDataTransferToPeer(transferId, 'MCU', completeFn, 'main',
- Object.keys(self._dataTransfers[transferId].peers.main));
- }
-
- if (Object.keys(self._dataTransfers[transferId].peers[transferId]).length > 0) {
- self._startDataTransferToPeer(transferId, 'MCU', completeFn, transferId,
- Object.keys(self._dataTransfers[transferId].peers[transferId]));
- }
- }
- };
-
- /**
- * Function that starts or listens the data transfer status to Peer.
- * This reacts differently during MCU environment.
- * @method _startDataTransferToPeer
- * @return {Boolean} Returns a Boolean only during MCU environment which flag indicates if Peer requires interop
- * (Use messaging Datachannel connection instead).
- * @private
- * @since 0.6.16
- */
- Skylink.prototype._startDataTransferToPeer = function (transferId, peerId, callback, channelProp, targetPeers) {
- var self = this;
- var peerConnectionStateCbFn = null;
- var dataChannelStateCbFn = null;
-
- /**
- * Emit event for Peers function.
- */
- var emitEventFn = function (cb) {
- var peers = targetPeers || [peerId];
- for (var i = 0; i < peers.length; i++) {
- cb(peers[i]);
- }
- };
-
- /**
- * Return error and trigger them if failed before or during data transfers function.
- */
- var returnErrorBeforeTransferFn = function (error) {
- // Replace if it is a MCU Peer errors for clear indication in error message
- var updatedError = peerId === 'MCU' ? error.replace(/Peer/g, 'MCU Peer') : error;
-
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, true, false), {
- message: new Error(updatedError),
- transferType: self.DATA_TRANSFER_TYPE.UPLOAD
- });
- });
- };
-
- /**
- * Send WRQ protocol to start data transfers.
- */
- var sendWRQFn = function () {
- var size = self._dataTransfers[transferId].size;
- var chunkSize = self._dataTransfers[transferId].chunkSize;
- var chunkType = self._dataTransfers[transferId].sessionChunkType;
-
- if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1) {
- log.warn([peerId, 'RTCDataChannel', transferId,
- 'Binary data chunks transfer is not yet supported with Peer connecting from ' +
- 'Android, iOS and C++ SDK. Fallbacking to binary string data chunks transfer.']);
-
- size = self._dataTransfers[transferId].enforceBSInfo.size;
- chunkSize = self._dataTransfers[transferId].enforceBSInfo.chunkSize;
- chunkType = 'string';
- }
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.WRQ,
- transferId: transferId,
- name: self._dataTransfers[transferId].name,
- size: size,
- originalSize: self._dataTransfers[transferId].originalSize,
- dataType: self._dataTransfers[transferId].sessionType,
- mimeType: self._dataTransfers[transferId].mimeType,
- chunkType: chunkType,
- chunkSize: chunkSize,
- timeout: self._dataTransfers[transferId].timeout,
- isPrivate: self._dataTransfers[transferId].isPrivate,
- sender: self._user.sid,
- agent: AdapterJS.webrtcDetectedBrowser,
- version: AdapterJS.webrtcDetectedVersion,
- target: targetPeers ? targetPeers : peerId
- }, channelProp);
-
- emitEventFn(function (evtPeerId) {
- self._trigger('incomingDataRequest', transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, false, false, false), true);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.USER_UPLOAD_REQUEST, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
- });
- };
-
- // Listen to data transfer state
- if (peerId !== 'MCU') {
- var dataTransferStateCbFn = function (state, evtTransferId, evtPeerId, transferInfo, error) {
- if (peerConnectionStateCbFn) {
- self.off('peerConnectionState', peerConnectionStateCbFn);
- }
-
- if (dataChannelStateCbFn) {
- self.off('dataChannelState', dataChannelStateCbFn);
- }
-
- if (channelProp) {
- delete self._dataTransfers[transferId].peers[channelProp][peerId];
- }
-
- callback(peerId, state === self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED ? null :
- error.message.message || error.message.toString());
-
- delete self._dataTransfers[transferId].sessions[peerId];
-
- if (self._hasMCU && Object.keys(self._dataTransfers[transferId].peers.main).length === 0 &&
- self._dataChannels.MCU && self._dataChannels.MCU.main) {
- self._dataChannels.MCU.main.transferId = null;
-
- } else if (channelProp === 'main' && self._dataChannels[peerId] && self._dataChannels[peerId].main) {
- self._dataChannels[peerId].main.transferId = null;
- }
-
- if (Object.keys(self._dataTransfers[transferId].sessions).length === 0) {
- delete self._dataTransfers[transferId];
- }
- };
-
- self.once('dataTransferState', dataTransferStateCbFn, function (state, evtTransferId, evtPeerId) {
- if (!(self._dataTransfers[transferId] && (self._hasMCU ?
- (self._dataTransfers[transferId].peers.main[peerId] || self._dataTransfers[transferId].peers[transferId][peerId]) :
- self._dataTransfers[transferId].sessions[peerId]))) {
-
- if (dataTransferStateCbFn) {
- self.off('dataTransferState', dataTransferStateCbFn);
- }
-
- if (peerConnectionStateCbFn) {
- self.off('peerConnectionState', peerConnectionStateCbFn);
- }
-
- if (dataChannelStateCbFn) {
- self.off('dataChannelState', dataChannelStateCbFn);
- }
- return;
- }
-
- return evtTransferId === transferId && evtPeerId === peerId && [
- self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED,
- self.DATA_TRANSFER_STATE.ERROR,
- self.DATA_TRANSFER_STATE.CANCEL,
- self.DATA_TRANSFER_STATE.REJECTED].indexOf(state) > -1;
- });
- }
-
- // When Peer connection does not exists
- if (!self._peerConnections[peerId]) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection does not exists.');
- return;
- }
-
- // When Peer session does not exists
- if (!self._peerInformations[peerId]) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection does not exists.');
- return;
- }
-
- // When Peer connection is not STABLE
- if (self._peerConnections[peerId].signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer connection is not stable.');
- return;
- }
-
- if (!self._dataTransfers[transferId]) {
- returnErrorBeforeTransferFn('Unable to start data transfer as data transfer session is not in order.');
- return;
- }
-
- if (!(self._dataChannels[peerId] && self._dataChannels[peerId].main)) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel connection does not exists.');
- return;
- }
-
- if (self._dataChannels[peerId].main.channel.readyState !== self.DATA_CHANNEL_STATE.OPEN) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel connection is not opened.');
- return;
- }
-
- var streamId = self._dataChannels[peerId].main.streamId;
-
- if (streamId && channelProp === 'main' && self._dataStreams[streamId] &&
- // Check if session chunk streaming is string and sending is string for Peer
- ((self._dataStreams[streamId].sessionChunkType === 'string' &&
- (self._dataTransfers[transferId].sessionChunkType === 'string' ||
- self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1)) ||
- // Check if session chunk streaming is binary and sending is binary for Peer
- (self._dataStreams[streamId].sessionChunkType === 'binary' &&
- self._dataStreams[streamId].sessionChunkType === 'binary' &&
- self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) === -1))) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel currently has an active ' +
- self._dataStreams[streamId].sessionChunkType + ' data streaming session.');
- return;
- }
-
- var protocolVer = (self._peerInformations[peerId].agent || {}).DTProtocolVersion || '0.1.0';
- var requireInterop = self._isLowerThanVersion(protocolVer, '0.1.2') || !self._initOptions.enableSimultaneousTransfers;
-
- // Prevent DATA_URL (or "string" dataType transfers) with Android / iOS / C++ SDKs
- if (self._isLowerThanVersion(protocolVer, '0.1.2') && self._dataTransfers[transferId].sessionType === 'data' &&
- self._dataTransfers[transferId].sessionChunkType === 'string') {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer do not support DATA_URL type of data transfers');
- return;
- }
-
- // Listen to Peer connection state for MCU Peer
- if (peerId !== 'MCU' && self._hasMCU) {
- channelProp = requireInterop ? 'main' : transferId;
-
- peerConnectionStateCbFn = function () {
- returnErrorBeforeTransferFn('Data transfer terminated as Peer connection is not stable.');
- };
-
- self.once('peerConnectionState', peerConnectionStateCbFn, function (state, evtPeerId) {
- if (!self._dataTransfers[transferId]) {
- self.off('peerConnectionState', peerConnectionStateCbFn);
- return;
- }
- return state !== self.PEER_CONNECTION_STATE.STABLE && evtPeerId === peerId;
- });
- return requireInterop;
- }
-
- if (requireInterop || channelProp === 'main') {
- // When MCU Datachannel connection has a transfer in-progress
- if (self._dataChannels[peerId].main.transferId) {
- returnErrorBeforeTransferFn('Unable to start data transfer as Peer Datachannel has a data transfer in-progress.');
- return;
- }
- }
-
- self._dataTransfers[transferId].sessions[peerId] = {
- timer: null,
- ackN: 0
- };
-
- dataChannelStateCbFn = function (state, evtPeerId, error) {
- // Prevent from triggering in instances where the ackN === chunks.length
- if (self._dataTransfers[transferId].sessions[peerId].ackN >= (self._dataTransfers[transferId].chunks.length - 1)) {
- return;
- }
-
- if (error) {
- returnErrorBeforeTransferFn(error.message || error.toString());
- } else {
- returnErrorBeforeTransferFn('Data transfer terminated as Peer Datachannel connection closed abruptly.');
- }
- };
-
- self.once('dataChannelState', dataChannelStateCbFn, function (state, evtPeerId, error, channelName, channelType) {
- if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- self.off('dataChannelState', dataChannelStateCbFn);
- return;
- }
-
- if (!(evtPeerId === peerId && (channelProp === 'main' ?
- channelType === self.DATA_CHANNEL_TYPE.MESSAGING : channelName === transferId))) {
- return;
- }
-
- if (state === self.DATA_CHANNEL_STATE.OPEN && channelProp !== 'main' && channelName === transferId) {
- self._dataChannels[peerId][channelProp].transferId = transferId;
- sendWRQFn();
- return false;
- }
-
- return [
- self.DATA_CHANNEL_STATE.CREATE_ERROR,
- self.DATA_CHANNEL_STATE.ERROR,
- self.DATA_CHANNEL_STATE.CLOSING,
- self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1;
- });
-
- // Create new Datachannel for Peer to start data transfer
- if (!((requireInterop && peerId !== 'MCU') || channelProp === 'main')) {
- channelProp = transferId;
- self._createDataChannel(peerId, transferId, self._dataTransfers[transferId].sessionType === 'data' ?
- self._CHUNK_DATAURL_SIZE : (self._dataTransfers[transferId].sessionChunkType === 'string' ?
- (AdapterJS.webrtcDetectedBrowser === 'firefox' ? 16384 : 65546) : // After conversion to base64 string computed size
- (AdapterJS.webrtcDetectedBrowser === 'firefox' ? self._MOZ_BINARY_FILE_SIZE : self._BINARY_FILE_SIZE)));
- } else {
- channelProp = 'main';
- self._dataChannels[peerId].main.transferId = transferId;
- sendWRQFn();
- }
- };
-
- /**
- * Function that returns the data transfer session.
- * @method _getTransferInfo
- * @private
- * @for Skylink
- * @since 0.6.16
- */
- Skylink.prototype._getTransferInfo = function (transferId, peerId, returnDataProp, hidePercentage, returnDataAtStart) {
- if (!this._dataTransfers[transferId]) {
- return {};
- }
-
- var transferInfo = {
- name: this._dataTransfers[transferId].name,
- size: this._dataTransfers[transferId].size,
- dataType: this._dataTransfers[transferId].dataType || this.DATA_TRANSFER_SESSION_TYPE.BLOB,
- mimeType: this._dataTransfers[transferId].mimeType || null,
- chunkSize: this._dataTransfers[transferId].chunkSize,
- chunkType: this._dataTransfers[transferId].chunkType,
- timeout: this._dataTransfers[transferId].timeout,
- isPrivate: this._dataTransfers[transferId].isPrivate,
- direction: this._dataTransfers[transferId].direction
- };
-
- if (this._dataTransfers[transferId].originalSize) {
- transferInfo.size = this._dataTransfers[transferId].originalSize;
-
- } else if (this._dataTransfers[transferId].chunkType === this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
- transferInfo.size = Math.ceil(transferInfo.size * 3 / 4);
- }
-
- if (!hidePercentage) {
- transferInfo.percentage = 0;
-
- if (!this._dataTransfers[transferId].sessions[peerId]) {
- if (returnDataProp) {
- transferInfo.data = null;
- }
- return transferInfo;
- }
-
- if (this._dataTransfers[transferId].direction === this.DATA_TRANSFER_TYPE.DOWNLOAD) {
- if (this._dataTransfers[transferId].sessions[peerId].receivedSize === this._dataTransfers[transferId].sessions[peerId].size) {
- transferInfo.percentage = 100;
-
- } else {
- transferInfo.percentage = parseFloat(((this._dataTransfers[transferId].sessions[peerId].receivedSize /
- this._dataTransfers[transferId].size) * 100).toFixed(2), 10);
- }
- } else {
- var chunksLength = (this._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1 ?
- this._dataTransfers[transferId].enforceBSInfo.chunks.length : this._dataTransfers[transferId].chunks.length);
-
- if (this._dataTransfers[transferId].sessions[peerId].ackN === chunksLength) {
- transferInfo.percentage = 100;
-
- } else {
- transferInfo.percentage = parseFloat(((this._dataTransfers[transferId].sessions[peerId].ackN /
- chunksLength) * 100).toFixed(2), 10);
- }
- }
-
- if (returnDataProp) {
- if (typeof returnDataAtStart !== 'number') {
- if (transferInfo.percentage === 100) {
- transferInfo.data = this._getTransferData(transferId);
- } else {
- transferInfo.data = null;
- }
- } else {
- transferInfo.percentage = returnDataAtStart;
-
- if (returnDataAtStart === 0) {
- transferInfo.data = this._getTransferData(transferId);
- }
- }
- }
- }
-
- return transferInfo;
- };
-
- /**
- * Function that returns the compiled data transfer data.
- * @method _getTransferData
- * @private
- * @for Skylink
- * @since 0.6.16
- */
- Skylink.prototype._getTransferData = function (transferId) {
- if (!this._dataTransfers[transferId]) {
- return null;
- }
-
- if (this._dataTransfers[transferId].dataType === this.DATA_TRANSFER_SESSION_TYPE.BLOB) {
- var mimeType = {
- name: this._dataTransfers[transferId].name
- };
-
- if (this._dataTransfers[transferId].mimeType) {
- mimeType.type = this._dataTransfers[transferId].mimeType;
- }
-
- return new Blob(this._dataTransfers[transferId].chunks, mimeType);
- }
-
- return this._dataTransfers[transferId].chunks.join('');
- };
-
- /**
- * Function that handles the data transfers sessions timeouts.
- * @method _handleDataTransferTimeoutForPeer
- * @private
- * @for Skylink
- * @since 0.6.16
- */
- Skylink.prototype._handleDataTransferTimeoutForPeer = function (transferId, peerId, setPeerTO) {
- var self = this;
-
- if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer does not exists for Peer. Ignoring timeout.']);
- return;
- }
-
- log.debug([peerId, 'RTCDataChannel', transferId, 'Clearing data transfer timer for Peer.']);
-
- if (self._dataTransfers[transferId].sessions[peerId].timer) {
- clearTimeout(self._dataTransfers[transferId].sessions[peerId].timer);
- }
-
- self._dataTransfers[transferId].sessions[peerId].timer = null;
-
- if (setPeerTO) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'Setting data transfer timer for Peer.']);
-
- self._dataTransfers[transferId].sessions[peerId].timer = setTimeout(function () {
- if (!(self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'Data transfer already ended for Peer. Ignoring expired timeout.']);
- return;
- }
-
- if (!(self._user && self._user.sid)) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'User is not in Room. Ignoring expired timeout.']);
- return;
- }
-
- if (!self._dataChannels[peerId]) {
- log.debug([peerId, 'RTCDataChannel', transferId, 'Datachannel connection does not exists. Ignoring expired timeout.']);
- return;
- }
-
- log.error([peerId, 'RTCDataChannel', transferId, 'Data transfer response has timed out.']);
-
- /**
- * Emit event for Peers function.
- */
- var emitEventFn = function (cb) {
- if (peerId === 'MCU') {
- var broadcastedPeers = [self._dataTransfers[transferId].peers.main,
- self._dataTransfers[transferId].peers[transferId]];
-
- for (var i = 0; i < broadcastedPeers.length; i++) {
- // Should not happen but insanity check
- if (!broadcastedPeers[i]) {
- continue;
- }
-
- for (var bcPeerId in broadcastedPeers[i]) {
- if (broadcastedPeers[i].hasOwnProperty(bcPeerId) && broadcastedPeers[i][bcPeerId]) {
- cb(bcPeerId);
- }
- }
- }
- } else {
- cb(peerId);
- }
- };
-
- var errorMsg = 'Connection Timeout. Longer than ' + self._dataTransfers[transferId].timeout +
- ' seconds. Connection is abolished.';
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ERROR,
- content: errorMsg,
- isUploadError: self._dataTransfers[transferId].direction === self.DATA_TRANSFER_TYPE.UPLOAD,
- sender: self._user.sid,
- name: self._dataTransfers[transferId].name
- }, self._dataChannels[peerId][transferId] ? transferId : 'main');
-
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, peerId,
- self._getTransferInfo(transferId, peerId, true, false, false), {
- transferType: self.DATA_TRANSFER_TYPE.DOWNLOAD,
- message: new Error(errorMsg)
- });
- });
- }, self._dataTransfers[transferId].timeout * 1000);
- }
- };
-
- /**
- * Function that handles the data received from Datachannel and
- * routes to the relevant data transfer protocol handler.
- * @method _processDataChannelData
- * @private
- * @for Skylink
- * @since 0.6.16
- */
- Skylink.prototype._processDataChannelData = function(rawData, peerId, channelName, channelType) {
- var self = this;
- var transferId = null;
- var streamId = null;
- var isStreamChunk = false;
- var channelProp = channelType === self.DATA_CHANNEL_TYPE.MESSAGING ? 'main' : channelName;
-
- // Safe access of _dataChannel object in case dataChannel has been closed unexpectedly | ESS-983
- var objPeerDataChannel = self._dataChannels[peerId] || {};
- if (objPeerDataChannel.hasOwnProperty(channelProp) && typeof objPeerDataChannel[channelProp] === 'object') {
- transferId = objPeerDataChannel[channelProp].transferId;
- streamId = objPeerDataChannel[channelProp].streamId;
- }
- else {
- return; // dataChannel not avaialble propbably having being closed abruptly | ESS-983
- }
-
- if (streamId && self._dataStreams[streamId]) {
- isStreamChunk = self._dataStreams[streamId].sessionChunkType === 'string' ? typeof rawData === 'string' :
- typeof rawData === 'object';
- }
-
- if (!self._peerConnections[peerId]) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping data received from Peer ' +
- 'as connection is not present ->'], rawData);
- return;
- }
-
- if (!(self._dataChannels[peerId] && self._dataChannels[peerId][channelProp])) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping data received from Peer ' +
- 'as Datachannel connection is not present ->'], rawData);
- return;
- }
-
- // Expect as string
- if (typeof rawData === 'string') {
- try {
- var protocolData = JSON.parse(rawData);
- isStreamChunk = false;
-
- log.debug([peerId, 'RTCDataChannel', channelProp, 'Received protocol "' + protocolData.type + '" message ->'], protocolData);
-
- // Ignore ACK, ERROR and CANCEL if there is no data transfer session in-progress
- if ([self._DC_PROTOCOL_TYPE.ACK, self._DC_PROTOCOL_TYPE.ERROR, self._DC_PROTOCOL_TYPE.CANCEL].indexOf(protocolData.type) > -1 &&
- !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded protocol message as data transfer session ' +
- 'is not present ->'], protocolData);
- return;
- }
-
- switch (protocolData.type) {
- case self._DC_PROTOCOL_TYPE.WRQ:
- // Discard iOS bidirectional upload when Datachannel is in-progress for data transfers
- if (transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId]) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Rejecting bidirectional data transfer request as ' +
- 'it is currently not supported in the SDK ->'], protocolData);
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ACK,
- ackN: -1,
- sender: self._user.sid
- }, channelProp);
- return;
- }
- self._WRQProtocolHandler(peerId, protocolData, channelProp);
- break;
- case self._DC_PROTOCOL_TYPE.ACK:
- self._ACKProtocolHandler(peerId, protocolData, channelProp);
- break;
- case self._DC_PROTOCOL_TYPE.ERROR:
- self._ERRORProtocolHandler(peerId, protocolData, channelProp);
- break;
- case self._DC_PROTOCOL_TYPE.CANCEL:
- self._CANCELProtocolHandler(peerId, protocolData, channelProp);
- break;
- case self._DC_PROTOCOL_TYPE.MESSAGE:
- self._MESSAGEProtocolHandler(peerId, protocolData, channelProp);
- break;
- default:
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded unknown "' + protocolData.type + '" message ->'], protocolData);
- }
-
- } catch (error) {
- if (rawData.indexOf('{') > -1 && rawData.indexOf('}') > 0) {
- log.error([peerId, 'RTCDataChannel', channelProp, 'Failed parsing protocol step data error ->'], {
- data: rawData,
- error: error
- });
-
- self._trigger('dataChannelState', self.DATA_CHANNEL_STATE.ERROR, peerId, error, channelName,
- channelType, null, self._getDataChannelBuffer(peerId, channelProp));
- throw error;
- }
-
- if (!isStreamChunk && !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded data chunk without session ->'], rawData);
- return;
- }
-
- if (!isStreamChunk && transferId) {
- if (self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN]) {
- log.warn([peerId, 'RTCDataChannel', transferId, 'Dropping data chunk ' + (!isStreamChunk ? '@' +
- self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' as it has already been added ->'], rawData);
- return;
- }
- }
-
- var chunkType = self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING;
-
- if (!isStreamChunk ? self._dataTransfers[transferId].dataType === self.DATA_TRANSFER_SESSION_TYPE.DATA_URL : true) {
- log.debug([peerId, 'RTCDataChannel', channelProp, 'Received string data chunk ' + (!isStreamChunk ? '@' +
- self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' with size ->'], rawData.length || rawData.size);
-
- self._DATAProtocolHandler(peerId, rawData, self.DATA_TRANSFER_DATA_TYPE.STRING,
- rawData.length || rawData.size || 0, channelProp);
-
- } else {
- var removeSpaceData = rawData.replace(/\s|\r|\n/g, '');
-
- log.debug([peerId, 'RTCDataChannel', channelProp, 'Received binary string data chunk @' +
- self._dataTransfers[transferId].sessions[peerId].ackN + ' with size ->'],
- removeSpaceData.length || removeSpaceData.size);
-
- self._DATAProtocolHandler(peerId, self._base64ToBlob(removeSpaceData), self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
- removeSpaceData.length || removeSpaceData.size || 0, channelProp);
- }
- }
- } else {
- if (!isStreamChunk && !(transferId && self._dataTransfers[transferId] && self._dataTransfers[transferId].sessions[peerId])) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Discarded data chunk without session ->'], rawData);
- return;
- }
-
- if (!isStreamChunk && transferId) {
- if (self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN]) {
- log.warn([peerId, 'RTCDataChannel', transferId, 'Dropping data chunk ' + (!isStreamChunk ? '@' +
- self._dataTransfers[transferId].sessions[peerId].ackN : '') + ' as it has already been added ->'], rawData);
- return;
- }
- }
-
- if (rawData instanceof Blob) {
- log.debug([peerId, 'RTCDataChannel', channelProp, 'Received blob data chunk ' + (isStreamChunk ? '' :
- '@' + self._dataTransfers[transferId].sessions[peerId].ackN) + ' with size ->'], rawData.size);
-
- self._DATAProtocolHandler(peerId, rawData, self.DATA_TRANSFER_DATA_TYPE.BLOB, rawData.size, channelProp);
-
- } else {
- var byteArray = rawData;
- var blob = null;
-
- // Plugin binary handling
- if (rawData.constructor && rawData.constructor.name === 'Array') {
- // Need to re-parse on some browsers
- byteArray = new Int8Array(rawData);
- }
-
- // Fallback for older IE versions
- if (AdapterJS.webrtcDetectedBrowser === 'IE') {
- if (window.BlobBuilder) {
- var bb = new BlobBuilder();
- bb.append(rawData.constructor && rawData.constructor.name === 'ArrayBuffer' ?
- byteArray : (new Uint8Array(byteArray)).buffer);
- blob = bb.getBlob();
- }
- } else {
- blob = new Blob([byteArray]);
- }
-
- log.debug([peerId, 'RTCDataChannel', channelProp, 'Received arraybuffer data chunk ' + (isStreamChunk ? '' :
- '@' + self._dataTransfers[transferId].sessions[peerId].ackN) + ' with size ->'], blob.size);
-
- self._DATAProtocolHandler(peerId, blob, self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER, blob.size, channelProp);
- }
- }
- };
-
- /**
- * Function that handles the "WRQ" data transfer protocol.
- * @method _WRQProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._WRQProtocolHandler = function(peerId, data, channelProp) {
- var self = this;
- var transferId = channelProp === 'main' ? data.transferId || null : channelProp;
- var senderPeerId = data.sender || peerId;
-
- if (['fastBinaryStart', 'fastBinaryStop'].indexOf(data.dataType) > -1) {
- if (data.dataType === 'fastBinaryStart') {
- if (!transferId) {
- transferId = 'stream_' + peerId + '_' + (new Date()).getTime();
- }
- self._dataStreams[transferId] = {
- chunkSize: 0,
- chunkType: data.chunkType === 'string' ? self.DATA_TRANSFER_DATA_TYPE.STRING : self._binaryChunkType,
- sessionChunkType: data.chunkType,
- isPrivate: !!data.isPrivate,
- isStringStream: data.chunkType === 'string',
- senderPeerId: senderPeerId,
- isUpload: false
- };
- self._dataChannels[peerId][channelProp].streamId = transferId;
- var hasStarted = false;
- self.once('dataChannelState', function () {}, function (state, evtPeerId, channelName, channelType, error) {
- if (!self._dataStreams[transferId]) {
- return true;
- }
-
- if (!(evtPeerId === peerId && (channelProp === 'main' ? channelType === self.DATA_CHANNEL_TYPE.MESSAGING :
- channelName === transferId && channelType === self.DATA_CHANNEL_TYPE.DATA))) {
- return;
- }
-
- if ([self.DATA_CHANNEL_STATE.ERROR, self.DATA_CHANNEL_STATE.CLOSED].indexOf(state) > -1) {
- var updatedError = new Error(error && error.message ? error.message :
- 'Failed data transfer as datachannel state is "' + state + '".');
-
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.ERROR, transferId, senderPeerId, {
- chunk: null,
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].chunkType,
- isPrivate: self._dataStreams[transferId].isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, updatedError);
- return true;
- }
- });
-
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVING_STARTED, transferId, senderPeerId, {
- chunk: null,
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].chunkType,
- isPrivate: self._dataStreams[transferId].isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, null);
- self._trigger('incomingDataStreamStarted', transferId, senderPeerId, {
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].chunkType,
- isPrivate: self._dataStreams[transferId].isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, false);
-
- } else {
- transferId = self._dataChannels[peerId][channelProp].streamId;
- if (self._dataStreams[transferId] && !self._dataStreams[transferId].isUpload) {
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVING_STOPPED, transferId, senderPeerId, {
- chunk: null,
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].chunkType,
- isPrivate: self._dataStreams[transferId].isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, null);
- self._trigger('incomingDataStreamStopped', transferId, senderPeerId, {
- chunkSize: 0,
- chunkType: self._dataStreams[transferId].chunkType,
- isPrivate: self._dataStreams[transferId].isPrivate,
- isStringStream: self._dataStreams[transferId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, false);
- self._dataChannels[peerId][channelProp].streamId = null;
- if (channelProp !== 'main') {
- self._closeDataChannel(peerId, channelProp);
- }
-
- delete self._dataStreams[transferId];
- }
- }
- } else {
- if (!transferId) {
- transferId = 'transfer_' + peerId + '_' + (new Date()).getTime();
- }
-
- self._dataTransfers[transferId] = {
- name: data.name || transferId,
- size: data.size || 0,
- chunkSize: data.chunkSize,
- originalSize: data.originalSize || 0,
- timeout: data.timeout || 60,
- isPrivate: !!data.isPrivate,
- senderPeerId: data.sender || peerId,
- dataType: self.DATA_TRANSFER_SESSION_TYPE.BLOB,
- mimeType: data.mimeType || null,
- chunkType: self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING,
- direction: self.DATA_TRANSFER_TYPE.DOWNLOAD,
- chunks: [],
- sessions: {},
- sessionType: data.dataType || 'blob',
- sessionChunkType: data.chunkType || 'string'
- };
-
- if (self._dataTransfers[transferId].sessionType === 'data' &&
- self._dataTransfers[transferId].sessionChunkType === 'string') {
- self._dataTransfers[transferId].dataType = self.DATA_TRANSFER_SESSION_TYPE.DATA_URL;
- self._dataTransfers[transferId].chunkType = self.DATA_TRANSFER_DATA_TYPE.STRING;
- } else if (self._dataTransfers[transferId].sessionType === 'blob' &&
- self._dataTransfers[transferId].sessionChunkType === 'binary') {
- self._dataTransfers[transferId].chunkType = self._binaryChunkType;
- }
-
- self._dataChannels[peerId][channelProp].transferId = transferId;
- self._dataTransfers[transferId].sessions[peerId] = {
- timer: null,
- ackN: 0,
- receivedSize: 0
- };
-
- self._trigger('incomingDataRequest', transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, false, false, false), false);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_REQUEST, transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
- }
- };
-
- /**
- * Function that handles the "ACK" data transfer protocol.
- * @method _ACKProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._ACKProtocolHandler = function(peerId, data, channelProp) {
- var self = this;
-
- var transferId = channelProp;
- var senderPeerId = data.sender || peerId;
-
- if (channelProp === 'main') {
- transferId = self._dataChannels[peerId].main.transferId;
- }
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
-
- /**
- * Emit event for Peers function.
- */
- var emitEventFn = function (cb) {
- if (peerId === 'MCU') {
- if (!self._dataTransfers[transferId].peers[channelProp]) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of ACK event as ' +
- 'Peers list does not exists']);
- return;
- }
- for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
- if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
- self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
- cb(evtPeerId);
- }
- }
- } else {
- cb(senderPeerId);
- }
- };
-
- if (data.ackN > -1) {
- if (data.ackN === 0) {
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_STARTED, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, false, 0), null);
- });
- } else if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1 ?
- data.ackN === self._dataTransfers[transferId].enforceBSInfo.chunks.length :
- data.ackN === self._dataTransfers[transferId].chunks.length) {
- self._dataTransfers[transferId].sessions[peerId].ackN = data.ackN;
-
- emitEventFn(function (evtPeerId) {
- self._trigger('incomingData', self._getTransferData(transferId), transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, false, false, false), true);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, false, 100), null);
- });
-
- if (self._dataChannels[peerId][channelProp]) {
- self._dataChannels[peerId][channelProp].transferId = null;
-
- if (channelProp !== 'main') {
- self._closeDataChannel(peerId, channelProp);
- }
- }
- return;
- }
-
- var uploadFn = function (chunk) {
- self._sendMessageToDataChannel(peerId, chunk, channelProp, true);
-
- if (data.ackN < self._dataTransfers[transferId].chunks.length) {
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOADING, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
- });
- }
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, true);
- };
-
- self._dataTransfers[transferId].sessions[peerId].ackN = data.ackN;
-
- if (self._dataTransfers[transferId].enforceBSPeers.indexOf(peerId) > -1) {
- self._blobToBase64(self._dataTransfers[transferId].enforceBSInfo.chunks[data.ackN], uploadFn);
- } else if (self._dataTransfers[transferId].chunkType === self.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
- self._blobToBase64(self._dataTransfers[transferId].chunks[data.ackN], uploadFn);
- } else if (self._dataTransfers[transferId].chunkType === self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
- self._blobToArrayBuffer(self._dataTransfers[transferId].chunks[data.ackN], uploadFn);
- } else {
- uploadFn(self._dataTransfers[transferId].chunks[data.ackN]);
- }
- } else {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.REJECTED, transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), {
- message: new Error('Data transfer terminated as Peer has rejected data transfer request'),
- transferType: self.DATA_TRANSFER_TYPE.UPLOAD
- });
- }
- };
-
- /**
- * Function that handles the "MESSAGE" data transfer protocol.
- * @method _MESSAGEProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._MESSAGEProtocolHandler = function(peerId, data, channelProp) {
- var senderPeerId = data.sender || peerId;
-
- log.log([senderPeerId, 'RTCDataChannel', channelProp, 'Received P2P message from peer:'], data);
-
- this._trigger('incomingMessage', {
- content: data.data,
- isPrivate: data.isPrivate,
- isDataChannel: true,
- targetPeerId: this._user.sid,
- senderPeerId: senderPeerId
- }, senderPeerId, this.getPeerInfo(senderPeerId), false);
- };
-
- /**
- * Function that handles the "ERROR" data transfer protocol.
- * @method _ERRORProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._ERRORProtocolHandler = function(peerId, data, channelProp) {
- var self = this;
-
- var transferId = channelProp;
- var senderPeerId = data.sender || peerId;
-
- if (channelProp === 'main') {
- transferId = self._dataChannels[peerId].main.transferId;
- }
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
-
- /**
- * Emit event for Peers function.
- */
- var emitEventFn = function (cb) {
- if (peerId === 'MCU') {
- if (!self._dataTransfers[transferId].peers[channelProp]) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of ERROR event as ' +
- 'Peers list does not exists']);
- return;
- }
- for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
- if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
- self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
- cb(evtPeerId);
- }
- }
- } else {
- cb(senderPeerId);
- }
- };
-
- log.error([peerId, 'RTCDataChannel', channelProp, 'Received an error from peer ->'], data);
-
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerID, true, false, false), {
- message: new Error(data.content),
- transferType: self._dataTransfers[transferId].direction
- });
- });
- };
-
- /**
- * Function that handles the "CANCEL" data transfer protocol.
- * @method _CANCELProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.0
- */
- Skylink.prototype._CANCELProtocolHandler = function(peerId, data, channelProp) {
- var self = this;
- var transferId = channelProp;
-
- if (channelProp === 'main') {
- transferId = self._dataChannels[peerId].main.transferId;
- }
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
-
- /**
- * Emit event for Peers function.
- */
- var emitEventFn = function (cb) {
- if (peerId === 'MCU') {
- if (!self._dataTransfers[transferId].peers[channelProp]) {
- log.warn([peerId, 'RTCDataChannel', channelProp, 'Dropping triggering of CANCEL event as ' +
- 'Peers list does not exists']);
- return;
- }
- for (var evtPeerId in self._dataTransfers[transferId].peers[channelProp]) {
- if (self._dataTransfers[transferId].peers[channelProp].hasOwnProperty(evtPeerId) &&
- self._dataTransfers[transferId].peers[channelProp][evtPeerId]) {
- cb(evtPeerId);
- }
- }
- } else {
- cb(peerId);
- }
- };
-
- log.error([peerId, 'RTCDataChannel', channelProp, 'Received data transfer termination from peer ->'], data);
-
- emitEventFn(function (evtPeerId) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.CANCEL, transferId, evtPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), {
- message: new Error(data.content || 'Peer has terminated data transfer.'),
- transferType: self._dataTransfers[transferId].direction
- });
- });
- };
-
- /**
- * Function that handles the data transfer chunk received.
- * @method _DATAProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.5
- */
- Skylink.prototype._DATAProtocolHandler = function(peerId, chunk, chunkType, chunkSize, channelProp) {
- var self = this;
- var transferId = channelProp;
- var senderPeerId = peerId;
-
- if (!(self._dataChannels[peerId] && self._dataChannels[peerId][channelProp])) {
- return;
- }
-
- var streamId = self._dataChannels[peerId][channelProp].streamId;
-
- if (streamId && self._dataStreams[streamId] && ((typeof chunk === 'string' &&
- self._dataStreams[streamId].sessionChunkType === 'string') || (chunk instanceof Blob &&
- self._dataStreams[streamId].sessionChunkType === 'binary'))) {
- senderPeerId = self._dataStreams[streamId].senderPeerId || peerId;
- self._trigger('dataStreamState', self.DATA_STREAM_STATE.RECEIVED, streamId, senderPeerId, {
- chunk: chunk,
- chunkSize: chunkSize,
- chunkType: chunkType,
- isPrivate: self._dataStreams[streamId].sessionChunkType.isPrivate,
- isStringStream: self._dataStreams[streamId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, null);
- self._trigger('incomingDataStream', chunk, transferId, senderPeerId, {
- chunkSize: chunkSize,
- chunkType: chunkType,
- isPrivate: self._dataStreams[streamId].sessionChunkType.isPrivate,
- isStringStream: self._dataStreams[streamId].sessionChunkType === 'string',
- senderPeerId: senderPeerId
- }, false);
- return;
- }
-
- if (channelProp === 'main') {
- transferId = self._dataChannels[peerId].main.transferId;
- }
-
- if (self._dataTransfers[transferId].senderPeerId) {
- senderPeerId = self._dataTransfers[transferId].senderPeerId;
- }
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, false);
-
- self._dataTransfers[transferId].chunkType = chunkType;
- self._dataTransfers[transferId].sessions[peerId].receivedSize += chunkSize;
- self._dataTransfers[transferId].chunks[self._dataTransfers[transferId].sessions[peerId].ackN] = chunk;
-
- if (self._dataTransfers[transferId].sessions[peerId].receivedSize >= self._dataTransfers[transferId].size) {
- log.log([peerId, 'RTCDataChannel', channelProp, 'Data transfer has been completed. Computed size ->'],
- self._dataTransfers[transferId].sessions[peerId].receivedSize);
-
- // Send last ACK to Peer to indicate completion of data transfers
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ACK,
- sender: self._user.sid,
- ackN: self._dataTransfers[transferId].sessions[peerId].ackN + 1
- }, channelProp);
-
- self._trigger('incomingData', self._getTransferData(transferId), transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, false, false, false), null);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED, transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
- return;
- }
-
- self._dataTransfers[transferId].sessions[peerId].ackN += 1;
-
- self._sendMessageToDataChannel(peerId, {
- type: self._DC_PROTOCOL_TYPE.ACK,
- sender: self._user.sid,
- ackN: self._dataTransfers[transferId].sessions[peerId].ackN
- }, channelProp);
-
- self._handleDataTransferTimeoutForPeer(transferId, peerId, true);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.DOWNLOADING, transferId, senderPeerId,
- self._getTransferInfo(transferId, peerId, true, false, false), null);
- };
-