- /**
- * <blockquote class="info">
- * Note that this is used only for SDK developer purposes.<br>
- * Current version: <code>0.1.0</code>
- * </blockquote>
- * The value of the current version of the data transfer protocol.
- * @attribute DT_PROTOCOL_VERSION
- * @type String
- * @readOnly
- * @for Skylink
- * @since 0.5.10
- */
- Skylink.prototype.DT_PROTOCOL_VERSION = '0.1.0';
-
- /**
- * The list of data transfers directions.
- * @attribute DATA_TRANSFER_TYPE
- * @param {String} UPLOAD <small>Value <code>"upload"</code></small>
- * The value of the data transfer direction when User is uploading data to Peer.
- * @param {String} DOWNLOAD <small>Value <code>"download"</code></small>
- * The value of the data transfer direction when User is downloading data from Peer.
- * @type JSON
- * @readOnly
- * @for Skylink
- * @since 0.1.0
- */
- Skylink.prototype.DATA_TRANSFER_TYPE = {
- UPLOAD: 'upload',
- DOWNLOAD: 'download'
- };
-
- /**
- * The list of data transfers session types.
- * @attribute DATA_TRANSFER_SESSION_TYPE
- * @param {String} BLOB <small>Value <code>"blob"</code></small>
- * The value of the session type for
- * <a href="#method_sendURLData"><code>sendURLData()</code> method</a> data transfer.
- * @param {String} DATA_URL <small>Value <code>"dataURL"</code></small>
- * The value of the session type for
- * <a href="#method_sendBlobData"><code>method_sendBlobData()</code> method</a> data transfer.
- * @type JSON
- * @readOnly
- * @for Skylink
- * @since 0.1.0
- */
- Skylink.prototype.DATA_TRANSFER_SESSION_TYPE = {
- BLOB: 'blob',
- DATA_URL: 'dataURL'
- };
-
- /**
- * The list of data transfer states.
- * @attribute DATA_TRANSFER_STATE
- * @param {String} UPLOAD_REQUEST <small>Value <code>"request"</code></small>
- * The value of the state when receiving an upload data transfer request from Peer to User.
- * <small>At this stage, the upload data transfer request from Peer may be accepted or rejected with the
- * <a href="#method_acceptDataTransfer"><code>acceptDataTransfer()</code> method</a>.</small>
- * @param {String} UPLOAD_STARTED <small>Value <code>"uploadStarted"</code></small>
- * The value of the state when the data transfer request has been accepted
- * and data transfer will start uploading data to Peer.
- * <small>At this stage, the data transfer may be terminated with the
- * <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>.</small>
- * @param {String} DOWNLOAD_STARTED <small>Value <code>"downloadStarted"</code></small>
- * The value of the state when the data transfer request has been accepted
- * and data transfer will start downloading data from Peer.
- * <small>At this stage, the data transfer may be terminated with the
- * <a href="#method_cancelDataTransfer"><code>cancelDataTransfer()</code> method</a>.</small>
- * @param {String} REJECTED <small>Value <code>"rejected"</code></small>
- * The value of the state when upload data transfer request to Peer has been rejected and terminated.
- * @param {String} UPLOADING <small>Value <code>"uploading"</code></small>
- * The value of the state when data transfer is uploading data to Peer.
- * @param {String} DOWNLOADING <small>Value <code>"downloading"</code></small>
- * The value of the state when data transfer is downloading data from Peer.
- * @param {String} UPLOAD_COMPLETED <small>Value <code>"uploadCompleted"</code></small>
- * The value of the state when data transfer has uploaded successfully to Peer.
- * @param {String} DOWNLOAD_COMPLETED <small>Value <code>"downloadCompleted"</code></small>
- * The value of the state when data transfer has downloaded successfully from Peer.
- * @param {String} CANCEL <small>Value <code>"cancel"</code></small>
- * The value of the state when data transfer has been terminated from / to Peer.
- * @param {String} ERROR <small>Value <code>"error"</code></small>
- * The value of the state when data transfer has errors and has been terminated from / to Peer.
- * @type JSON
- * @readOnly
- * @for Skylink
- * @since 0.4.0
- */
- Skylink.prototype.DATA_TRANSFER_STATE = {
- UPLOAD_REQUEST: 'request',
- UPLOAD_STARTED: 'uploadStarted',
- DOWNLOAD_STARTED: 'downloadStarted',
- REJECTED: 'rejected',
- CANCEL: 'cancel',
- ERROR: 'error',
- UPLOADING: 'uploading',
- DOWNLOADING: 'downloading',
- UPLOAD_COMPLETED: 'uploadCompleted',
- DOWNLOAD_COMPLETED: 'downloadCompleted'
- };
-
- /**
- * Stores the fixed delimiter that concats the Datachannel label and actual transfer ID.
- * @attribute _TRANSFER_DELIMITER
- * @type String
- * @readOnly
- * @private
- * @for Skylink
- * @since 0.5.10
- */
- Skylink.prototype._TRANSFER_DELIMITER = '_skylink__';
-
- /**
- * Stores the list of data transfer protocols.
- * @attribute _DC_PROTOCOL_TYPE
- * @param {String} WRQ The protocol to initiate data transfer.
- * @param {String} ACK The protocol to request for data transfer chunk.
- * Give <code>-1</code> to reject the request at the beginning and <code>0</code> to accept
- * the data transfer request.
- * @param {String} CANCEL The protocol to terminate data transfer.
- * @param {String} ERROR The protocol when data transfer has errors and has to be terminated.
- * @param {String} MESSAGE The protocol that is used to send P2P messages.
- * @type JSON
- * @readOnly
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._DC_PROTOCOL_TYPE = {
- WRQ: 'WRQ',
- ACK: 'ACK',
- ERROR: 'ERROR',
- CANCEL: 'CANCEL',
- MESSAGE: 'MESSAGE'
- };
-
- /**
- * Stores the list of types of SDKs that do not support simultaneous data transfers.
- * @attribute _INTEROP_MULTI_TRANSFERS
- * @type Array
- * @readOnly
- * @private
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype._INTEROP_MULTI_TRANSFERS = ['Android', 'iOS'];
-
- /**
- * Stores the list of uploading data transfers chunks to Peers.
- * @attribute _uploadDataTransfers
- * @param {Array} <#transferId> The uploading data transfer chunks.
- * @type JSON
- * @private
- * @for Skylink
- * @since 0.4.1
- */
- Skylink.prototype._uploadDataTransfers = {};
-
- /**
- * Stores the list of uploading data transfer sessions to Peers.
- * @attribute _uploadDataSessions
- * @param {JSON} <#transferId> The uploading data transfer session.
- * @type JSON
- * @private
- * @for Skylink
- * @since 0.4.1
- */
- Skylink.prototype._uploadDataSessions = {};
-
- /**
- * Stores the list of downloading data transfers chunks to Peers.
- * @attribute _downloadDataTransfers
- * @param {Array} <#transferId> The downloading data transfer chunks.
- * @type JSON
- * @private
- * @for Skylink
- * @since 0.4.1
- */
- Skylink.prototype._downloadDataTransfers = {};
-
- /**
- * Stores the list of downloading data transfer sessions to Peers.
- * @attribute _downloadDataSessions
- * @param {JSON} <#transferId> The downloading data transfer session.
- * @type JSON
- * @private
- * @for Skylink
- * @since 0.4.1
- */
- Skylink.prototype._downloadDataSessions = {};
-
- /**
- * Stores the list of data transfer "wait-for-response" timeouts.
- * @attribute _dataTransfersTimeout
- * @param {Object} <#transferId> The data transfer session "wait-for-response" timeout.
- * @type JSON
- * @private
- * @for Skylink
- * @since 0.4.1
- */
- Skylink.prototype._dataTransfersTimeout = {};
-
- /**
- * <blockquote class="info">
- * Note that Android and iOS SDKs do not support simultaneous data transfers.
- * </blockquote>
- * 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 {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 <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.error.transferInfo.transferId</code> instead.
- * </blockquote> The data transfer ID.
- * <small>Defined only for single targeted Peer data transfer.</small>
- * @param {String} callback.error.state <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <a href="#event_dataTransferState"><code>dataTransferState</code>
- * event</a> instead. </blockquote> The data transfer state that resulted in error.
- * <small>Defined only for single targeted Peer data transfer.</small> [Rel: Skylink.DATA_TRANSFER_STATE]
- * @param {String} callback.error.peerId <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.error.listOfPeers</code> instead.
- * </blockquote> The targeted Peer ID for data transfer.
- * <small>Defined only for single targeted Peer data transfer.</small>
- * @param {Boolean} callback.error.isPrivate <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.error.transferInfo.isPrivate</code> instead.
- * </blockquote> The flag if data transfer is targeted or not, basing
- * off the <code>targetPeerId</code> parameter being defined.
- * <small>Defined only for single targeted Peer data transfer.</small>
- * @param {Error|String} callback.error.error <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.error.transferErrors</code> instead.
- * </blockquote> The error received that resulted in error.
- * <small>Defined only for single targeted Peer 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>.</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 <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.success.transferInfo.transferId</code> instead.
- * </blockquote> The data transfer ID.
- * @param {String} callback.success.state <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <a href="#event_dataTransferState"><code>dataTransferState</code>
- * event</a> instead. </blockquote> The data transfer state that resulted in error.
- * <small>Defined only for single targeted Peer data transfer.</small> [Rel: Skylink.DATA_TRANSFER_STATE]
- * @param {String} callback.success.peerId <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.success.listOfPeers</code> instead.
- * </blockquote> The targeted Peer ID for data transfer.
- * <small>Defined only for single targeted Peer data transfer.</small>
- * @param {Boolean} callback.success.isPrivate <blockquote class="info">
- * <b>Deprecation Warning!</b> This property has been deprecated.
- * Please use <code>callback.success.transferInfo.isPrivate</code> instead.
- * </blockquote> The flag if data transfer is targeted or not, basing
- * off the <code>targetPeerId</code> parameter being defined.
- * <small>Defined only for single targeted Peer data transfer.</small>
- * @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>.</small>
- * @trigger <ol class="desc-seq">
- * <li>Checks if should open a new Datachannel <ol>
- * <li>If Peer connection has closed: <small>This can be checked with <a href="#event_peerConnectionState">
- * <code>peerConnectionState</code> event</a> triggering parameter payload <code>state</code> as <code>CLOSED</code>
- * for Peer.</small> <ol><li><b>ABORT</b> step and return error.</li></ol></li>
- * <li>If Peer supports simultaneous data transfer, open new Datachannel: <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>.</li>
- * <li>If Datachannel has 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></ol></li>
- * <li>Else: <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><b>ABORT</b> step and return error.</li></ol></li></ol></li></ol></li>
- * <li>Starts the data transfer to Peer <ol>
- * <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><a href="#event_incomingDataRequest"><code>incomingDataRequest</code> event</a> triggers.</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 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>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>
- * <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><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>
- * <li><a href="#event_incomingData"><code>incomingData</code> event</a> triggers.</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><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, callback) {
- var listOfPeers = Object.keys(this._peerConnections);
- var isPrivate = false;
- var dataInfo = {};
- var transferId = this._user.sid + this.DATA_TRANSFER_TYPE.UPLOAD +
- (((new Date()).toISOString().replace(/-/g, '').replace(/:/g, ''))).replace('.', '');
- // for error case
- var errorMsg, errorPayload, i, peerId; // for jshint
- var singleError = null;
- var transferErrors = {};
- var stateError = null;
- var singlePeerId = null;
-
- //Shift parameters
- // timeout
- if (typeof timeout === 'function') {
- callback = timeout;
-
- } else if (typeof timeout === 'string') {
- listOfPeers = [timeout];
- isPrivate = true;
-
- } else if (Array.isArray(timeout)) {
- listOfPeers = timeout;
- isPrivate = true;
- }
-
- // targetPeerId
- if (typeof targetPeerId === 'function'){
- callback = targetPeerId;
-
- // data, timeout, target [array], callback
- } else if(Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- isPrivate = true;
-
- // data, timeout, target [string], callback
- } else if (typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- isPrivate = true;
- }
-
- //state: String, Deprecated. But for consistency purposes. Null if not a single peer
- //error: Object, Deprecated. But for consistency purposes. Null if not a single peer
- //transferId: String,
- //peerId: String, Deprecated. But for consistency purposes. Null if not a single peer
- //listOfPeers: Array, NEW!!
- //isPrivate: isPrivate, NEW!!
- //transferErrors: JSON, NEW!! - Array of errors
- //transferInfo: JSON The same payload as dataTransferState transferInfo payload
-
- // check if it's blob data
- if (!(typeof data === 'object' && data instanceof Blob)) {
- errorMsg = 'Provided data is not a Blob data';
-
- if (listOfPeers.length === 0) {
- transferErrors.self = errorMsg;
-
- } else {
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
- transferErrors[peerId] = errorMsg;
- }
-
- // Deprecated but for consistency purposes. Null if not a single peer.
- if (listOfPeers.length === 1 && isPrivate) {
- stateError = self.DATA_TRANSFER_STATE.ERROR;
- singleError = errorMsg;
- singlePeerId = listOfPeers[0];
- }
- }
-
- errorPayload = {
- state: stateError,
- error: singleError,
- transferId: transferId,
- peerId: singlePeerId,
- listOfPeers: listOfPeers,
- transferErrors: transferErrors,
- transferInfo: dataInfo,
- isPrivate: isPrivate
- };
-
- log.error(errorMsg, errorPayload);
-
- if (typeof callback === 'function'){
- log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
- 'with error -> '],errorPayload);
- callback(errorPayload, null);
- }
- return;
- }
-
- // populate data
- dataInfo.name = data.name || transferId;
- dataInfo.size = data.size;
- dataInfo.timeout = typeof timeout === 'number' ? timeout : 60;
- dataInfo.transferId = transferId;
- dataInfo.dataType = 'blob';
- dataInfo.isPrivate = isPrivate;
-
- // check if datachannel is enabled first or not
- if (!this._enableDataChannel) {
- errorMsg = 'Unable to send any blob data. Datachannel is disabled';
-
- if (listOfPeers.length === 0) {
- transferErrors.self = errorMsg;
-
- } else {
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
- transferErrors[peerId] = errorMsg;
- }
-
- // Deprecated but for consistency purposes. Null if not a single peer.
- if (listOfPeers.length === 1 && isPrivate) {
- stateError = self.DATA_TRANSFER_STATE.ERROR;
- singleError = errorMsg;
- singlePeerId = listOfPeers[0];
- }
- }
-
- errorPayload = {
- state: stateError,
- error: singleError,
- transferId: transferId,
- peerId: singlePeerId,
- listOfPeers: listOfPeers,
- transferErrors: transferErrors,
- transferInfo: dataInfo,
- isPrivate: isPrivate
- };
-
- log.error(errorMsg, errorPayload);
-
- if (typeof callback === 'function'){
- log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
- 'with error -> '], errorPayload);
- callback(errorPayload, null);
- }
- return;
- }
-
- this._startDataTransfer(data, dataInfo, listOfPeers, callback);
- };
-
- /**
- * <blockquote class="info">
- * <b>Deprecation Warning!</b> This method has been deprecated, please use <a href="#method_acceptDataTransfer">
- * <code>acceptDataTransfer()</code> method</a> instead.
- * </blockquote>
- * Function that accepts or rejects an upload data transfer request from Peer to User.
- * <small>Parameter signature follows <a href="#method_acceptDataTransfer">
- * <code>acceptDataTransfer()</code> method</a>.</small>
- * @method respondBlobRequest
- * @example
- * // Example 1: Accept Peer upload data transfer request
- * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
- * if (!isSelf) {
- * skylinkDemo.respondBlobRequest(peerId, transferId, true);
- * }
- * });
- *
- * // Example 2: Reject Peer upload data transfer request
- * skylinkDemo.on("incomingDataRequest", function (transferId, peerId, transferInfo, isSelf) {
- * if (!isSelf) {
- * skylinkDemo.respondBlobRequest(peerId, transferId, false);
- * }
- * });
- * @deprecated true
- * @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.5.0
- */
- Skylink.prototype.respondBlobRequest =
- /**
- * 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.acceptDataTransfer = function (peerId, transferId, accept) {
- if (typeof transferId !== 'string' && typeof peerId !== 'string') {
- log.error([peerId, 'RTCDataChannel', null, 'Aborting accept data transfer as ' +
- 'transfer ID and peer ID is not provided'], {
- accept: accept,
- peerId: peerId,
- transferId: transferId
- });
- return;
- }
-
- if (transferId.indexOf(this._TRANSFER_DELIMITER) === -1) {
- log.error([peerId, 'RTCDataChannel', null, 'Aborting accept data transfer as ' +
- 'invalid transfer ID is provided'], {
- accept: accept,
- transferId: transferId
- });
- return;
- }
- var channelName = transferId.split(this._TRANSFER_DELIMITER)[0];
-
- if (accept) {
-
- log.info([peerId, 'RTCDataChannel', channelName, 'User accepted peer\'s request'], {
- accept: accept,
- transferId: transferId
- });
-
- if (!this._peerInformations[peerId] && !this._peerInformations[peerId].agent) {
- log.error([peerId, 'RTCDataChannel', channelName, 'Aborting accept data transfer as ' +
- 'Peer informations for peer is missing'], {
- accept: accept,
- transferId: transferId
- });
- return;
- }
-
- this._downloadDataTransfers[channelName] = [];
-
- var data = this._downloadDataSessions[channelName];
- this._sendDataChannelMessage(peerId, {
- type: this._DC_PROTOCOL_TYPE.ACK,
- sender: this._user.sid,
- ackN: 0,
- agent: window.webrtcDetectedBrowser
- }, channelName);
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOAD_STARTED,
- data.transferId, peerId, {
- name: data.name,
- size: data.size,
- data: null,
- dataType: data.dataType,
- percentage: 0,
- senderPeerId: peerId,
- timeout: data.timeout,
- isPrivate: data.isPrivate
- });
- } else {
- log.info([peerId, 'RTCDataChannel', channelName, 'User rejected peer\'s request'], {
- accept: accept,
- transferId: transferId
- });
- this._sendDataChannelMessage(peerId, {
- type: this._DC_PROTOCOL_TYPE.ACK,
- sender: this._user.sid,
- ackN: -1
- }, channelName);
- delete this._downloadDataSessions[channelName];
- delete this._downloadDataTransfers[channelName];
- }
- };
-
- /**
- * <blockquote class="info">
- * <b>Deprecation Warning!</b> This method has been deprecated, please use <a href="#method_cancelDataTransfer">
- * <code>method_cancelDataTransfer()</code> method</a> instead.
- * </blockquote>
- * Function that terminates a currently uploading / downloading data transfer from / to Peer.
- * <small>Parameter signature follows <a href="#method_cancelDataTransfer">
- * <code>cancelDataTransfer()</code> method</a>.</small>
- * @method cancelBlobTransfer
- * @trigger <small>Event sequence follows <a href="#method_acceptDataTransfer">
- * @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.cancelBlobTransfer(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
- * @deprecated true
- * @for Skylink
- * @since 0.5.7
- */
- Skylink.prototype.cancelBlobTransfer =
- /**
- * 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.cancelDataTransfer = function (peerId, transferId) {
- var data;
-
- // targetPeerId + '-' + transferId
- var channelName = peerId + '-' + transferId;
-
- if (transferId.indexOf(this._TRANSFER_DELIMITER) > 0) {
- channelName = transferId.split(this._TRANSFER_DELIMITER)[0];
- } else {
-
- var peerAgent = (this._peerInformations[peerId] || {}).agent;
-
- if (!peerAgent && !peerAgent.name) {
- log.error([peerId, 'RTCDataChannel', null, 'Cancel transfer to peer ' +
- 'failed as peer agent information for peer does not exists'], transferId);
- return;
- }
-
- if (self._INTEROP_MULTI_TRANSFERS.indexOf(peerAgent.name) > -1) {
- channelName = peerId;
- }
- }
-
- if (this._uploadDataSessions[channelName]) {
- data = this._uploadDataSessions[channelName];
-
- delete this._uploadDataSessions[channelName];
- delete this._uploadDataTransfers[channelName];
-
- // send message
- this._sendDataChannelMessage(peerId, {
- type: this._DC_PROTOCOL_TYPE.CANCEL,
- sender: this._user.sid,
- name: data.name,
- content: 'Peer cancelled upload transfer'
- }, channelName);
-
- log.debug([peerId, 'RTCDataChannel', channelName,
- 'Cancelling upload data transfers'], transferId);
-
- } else if (this._downloadDataSessions[channelName]) {
- data = this._downloadDataSessions[channelName];
-
- delete this._downloadDataSessions[channelName];
- delete this._downloadDataTransfers[channelName];
-
- // send message
- this._sendDataChannelMessage(peerId, {
- type: this._DC_PROTOCOL_TYPE.CANCEL,
- sender: this._user.sid,
- name: data.name,
- content: 'Peer cancelled download transfer'
- }, channelName);
-
- log.debug([peerId, 'RTCDataChannel', channelName,
- 'Cancelling download data transfers'], transferId);
-
- } else {
- log.error([peerId, 'RTCDataChannel', null, 'Cancel transfer to peer ' +
- 'failed as transfer session with peer does not exists'], transferId);
- return;
- }
-
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.CANCEL,
- data.transferId, peerId, {
- name: data.name,
- size: data.size,
- percentage: data.percentage,
- data: null,
- dataType: data.dataType,
- senderPeerId: data.senderPeerId,
- timeout: data.timeout,
- isPrivate: data.isPrivate
- });
- };
-
- /**
- * 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 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><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 self = this;
-
- // check if datachannel is enabled first or not
- if (!self._enableDataChannel) {
- log.warn('Unable to send any P2P message. Datachannel is disabled');
- return;
- }
-
- var listOfPeers = Object.keys(self._dataChannels);
- var isPrivate = false;
-
- //targetPeerId is defined -> private message
- if (Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- isPrivate = true;
-
- } else if (typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- isPrivate = true;
- }
-
- // sending public message to MCU to relay. MCU case only
- if (self._hasMCU) {
- if (isPrivate) {
- log.log(['MCU', null, null, 'Relaying private P2P message to peers'], listOfPeers);
- self._sendDataChannelMessage('MCU', {
- type: self._DC_PROTOCOL_TYPE.MESSAGE,
- isPrivate: isPrivate,
- sender: self._user.sid,
- target: listOfPeers,
- data: message
- });
- } else {
- log.log(['MCU', null, null, 'Relaying P2P message to peers']);
-
- self._sendDataChannelMessage('MCU', {
- type: self._DC_PROTOCOL_TYPE.MESSAGE,
- isPrivate: isPrivate,
- sender: self._user.sid,
- target: 'MCU',
- data: message
- });
- }
- } else {
- for (var i = 0; i < listOfPeers.length; i++) {
- var peerId = listOfPeers[i];
- var useChannel = (self._hasMCU) ? 'MCU' : peerId;
-
- // Ignore MCU peer
- if (peerId === 'MCU') {
- continue;
- }
-
- log.log([peerId, null, useChannel, 'Sending P2P message to peer']);
-
- self._sendDataChannelMessage(useChannel, {
- type: self._DC_PROTOCOL_TYPE.MESSAGE,
- isPrivate: isPrivate,
- sender: self._user.sid,
- target: peerId,
- data: message
- });
- }
- }
-
- self._trigger('incomingMessage', {
- content: message,
- isPrivate: isPrivate,
- targetPeerId: targetPeerId || null,
- isDataChannel: true,
- senderPeerId: self._user.sid
- }, self._user.sid, self.getPeerInfo(), true);
- };
-
- /**
- * <blockquote class="info">
- * Currently, the Android and iOS SDKs do not support this type of data transfer.
- * </blockquote>
- * Function that starts an uploading string data transfer from User to Peers.
- * @method sendURLData
- * @param {String} data The data string to transfer to Peer.
- * <small>Parameter signature follows <a href="#method_sendBlobData">
- * <code>sendBlobData()</code> method</a> except <code>data</code> parameter.</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) {
- var listOfPeers = Object.keys(this._peerConnections);
- var isPrivate = false;
- var dataInfo = {};
- var transferId = this._user.sid + this.DATA_TRANSFER_TYPE.UPLOAD +
- (((new Date()).toISOString().replace(/-/g, '').replace(/:/g, ''))).replace('.', '');
- // for error case
- var errorMsg, errorPayload, i, peerId; // for jshint
- var singleError = null;
- var transferErrors = {};
- var stateError = null;
- var singlePeerId = null;
-
- //Shift parameters
- // timeout
- if (typeof timeout === 'function') {
- callback = timeout;
-
- } else if (typeof timeout === 'string') {
- listOfPeers = [timeout];
- isPrivate = true;
-
- } else if (Array.isArray(timeout)) {
- listOfPeers = timeout;
- isPrivate = true;
- }
-
- // targetPeerId
- if (typeof targetPeerId === 'function'){
- callback = targetPeerId;
-
- // data, timeout, target [array], callback
- } else if(Array.isArray(targetPeerId)) {
- listOfPeers = targetPeerId;
- isPrivate = true;
-
- // data, timeout, target [string], callback
- } else if (typeof targetPeerId === 'string') {
- listOfPeers = [targetPeerId];
- isPrivate = true;
- }
-
- //state: String, Deprecated. But for consistency purposes. Null if not a single peer
- //error: Object, Deprecated. But for consistency purposes. Null if not a single peer
- //transferId: String,
- //peerId: String, Deprecated. But for consistency purposes. Null if not a single peer
- //listOfPeers: Array, NEW!!
- //isPrivate: isPrivate, NEW!!
- //transferErrors: JSON, NEW!! - Array of errors
- //transferInfo: JSON The same payload as dataTransferState transferInfo payload
-
- // check if it's blob data
- if (typeof data !== 'string') {
- errorMsg = 'Provided data is not a dataURL';
-
- if (listOfPeers.length === 0) {
- transferErrors.self = errorMsg;
-
- } else {
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
- transferErrors[peerId] = errorMsg;
- }
-
- // Deprecated but for consistency purposes. Null if not a single peer.
- if (listOfPeers.length === 1 && isPrivate) {
- stateError = self.DATA_TRANSFER_STATE.ERROR;
- singleError = errorMsg;
- singlePeerId = listOfPeers[0];
- }
- }
-
- errorPayload = {
- state: stateError,
- error: singleError,
- transferId: transferId,
- peerId: singlePeerId,
- listOfPeers: listOfPeers,
- transferErrors: transferErrors,
- transferInfo: dataInfo,
- isPrivate: isPrivate
- };
-
- log.error(errorMsg, errorPayload);
-
- if (typeof callback === 'function'){
- log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
- 'with error -> '],errorPayload);
- callback(errorPayload, null);
- }
- return;
- }
-
- // populate data
- dataInfo.name = data.name || transferId;
- dataInfo.size = data.size || data.length;
- dataInfo.timeout = typeof timeout === 'number' ? timeout : 60;
- dataInfo.transferId = transferId;
- dataInfo.dataType = 'dataURL';
- dataInfo.isPrivate = isPrivate;
-
- // check if datachannel is enabled first or not
- if (!this._enableDataChannel) {
- errorMsg = 'Unable to send any dataURL. Datachannel is disabled';
-
- if (listOfPeers.length === 0) {
- transferErrors.self = errorMsg;
-
- } else {
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
- transferErrors[peerId] = errorMsg;
- }
-
- // Deprecated but for consistency purposes. Null if not a single peer.
- if (listOfPeers.length === 1 && isPrivate) {
- stateError = self.DATA_TRANSFER_STATE.ERROR;
- singleError = errorMsg;
- singlePeerId = listOfPeers[0];
- }
- }
-
- errorPayload = {
- state: stateError,
- error: singleError,
- transferId: transferId,
- peerId: singlePeerId,
- listOfPeers: listOfPeers,
- transferErrors: transferErrors,
- transferInfo: dataInfo,
- isPrivate: isPrivate
- };
-
- log.error(errorMsg, errorPayload);
-
- if (typeof callback === 'function'){
- log.log([null, 'RTCDataChannel', null, 'Error occurred. Firing callback ' +
- 'with error -> '], errorPayload);
- callback(errorPayload, null);
- }
- return;
- }
-
- this._startDataTransfer(data, dataInfo, listOfPeers, callback);
- };
-
- /**
- * Function that sets the data transfer "wait-for-response" timeout.
- * When there is not response after timeout, the data transfer will be terminated.
- * @method _setDataChannelTimeout
- * @private
- * @for Skylink
- * @since 0.5.0
- */
- Skylink.prototype._setDataChannelTimeout = function(peerId, timeout, isSender, channelName) {
- var self = this;
- if (!self._dataTransfersTimeout[channelName]) {
- self._dataTransfersTimeout[channelName] = null;
- }
- var type = (isSender) ? self.DATA_TRANSFER_TYPE.UPLOAD :
- self.DATA_TRANSFER_TYPE.DOWNLOAD;
-
- self._dataTransfersTimeout[channelName] = setTimeout(function() {
- var name;
- if (self._dataTransfersTimeout[channelName][type]) {
- if (isSender) {
- name = self._uploadDataSessions[channelName].name;
- delete self._uploadDataTransfers[channelName];
- delete self._uploadDataSessions[channelName];
- } else {
- name = self._downloadDataSessions[channelName].name;
- delete self._downloadDataTransfers[channelName];
- delete self._downloadDataSessions[channelName];
- }
-
- self._sendDataChannelMessage(peerId, {
- type: self._DC_PROTOCOL_TYPE.ERROR,
- sender: self._user.sid,
- name: name,
- content: 'Connection Timeout. Longer than ' + timeout +
- ' seconds. Connection is abolished.',
- isUploadError: isSender
- }, channelName);
- // TODO: Find a way to add channel name so it's more specific
- log.error([peerId, 'RTCDataChannel', channelName, 'Failed transfering data:'],
- 'Transfer ' + ((isSender) ? 'for': 'from') + ' ' + peerId +
- ' failed. Connection timeout');
- self._clearDataChannelTimeout(peerId, isSender, channelName);
- }
- }, 1000 * timeout);
- };
-
- /**
- * Function that stops and clears the data transfer "wait-for-response" timeout.
- * @method _clearDataChannelTimeout
- * @private
- * @for Skylink
- * @since 0.5.0
- */
- Skylink.prototype._clearDataChannelTimeout = function(peerId, isSender, channelName) {
- if (this._dataTransfersTimeout[channelName]) {
- clearTimeout(this._dataTransfersTimeout[channelName]);
- delete this._dataTransfersTimeout[channelName];
- log.debug([peerId, 'RTCDataChannel', channelName, 'Clear datachannel timeout']);
- } else {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Unable to find timeouts. ' +
- 'Not clearing the datachannel timeouts']);
- }
- };
-
- /**
- * Function that starts a data transfer to Peer.
- * This will open a new data type of Datachannel connection with Peer if
- * simultaneous data transfers is supported by Peer.
- * @method _sendBlobDataToPeer
- * @private
- * @for Skylink
- * @since 0.5.5
- */
- Skylink.prototype._sendBlobDataToPeer = function(data, dataInfo, targetPeerId) {
- var self = this;
- //If there is MCU then directs all messages to MCU
- var targetChannel = targetPeerId;//(self._hasMCU) ? 'MCU' : targetPeerId;
- var targetPeerList = [];
-
- var binarySize = parseInt((dataInfo.size * (4 / 3)).toFixed(), 10);
- var binaryChunkSize = 0;
- var chunkSize = 0;
- var i;
- var hasSend = false;
-
- // move list of peers to targetPeerList
- if (self._hasMCU) {
- if (Array.isArray(targetPeerList)) {
- targetPeerList = targetPeerId;
- } else {
- targetPeerList = [targetPeerId];
- }
- targetPeerId = 'MCU';
- }
-
- if (dataInfo.dataType !== 'blob') {
- // output: 1616
- binaryChunkSize = self._CHUNK_DATAURL_SIZE;
- chunkSize = self._CHUNK_DATAURL_SIZE;
- binarySize = dataInfo.size;
- } else if (window.webrtcDetectedBrowser === 'firefox') {
- // output: 16384
- binaryChunkSize = self._MOZ_CHUNK_FILE_SIZE * (4 / 3);
- chunkSize = self._MOZ_CHUNK_FILE_SIZE;
- } else {
- // output: 65536
- binaryChunkSize = parseInt((self._CHUNK_FILE_SIZE * (4 / 3)).toFixed(), 10);
- chunkSize = self._CHUNK_FILE_SIZE;
- }
-
- var throwTransferErrorFn = function (message) {
- // MCU targetPeerId case - list of peers
- if (self._hasMCU) {
- for (i = 0; i < targetPeerList.length; i++) {
- var peerId = targetPeerList[i];
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
- dataInfo.transferId, peerId, {
- name: dataInfo.name,
- size: dataInfo.size,
- percentage: 0,
- data: null,
- dataType: dataInfo.dataType,
- senderPeerId: self._user.sid,
- timeout: dataInfo.timeout,
- isPrivate: dataInfo.isPrivate
- },{
- message: message,
- transferType: self.DATA_TRANSFER_TYPE.UPLOAD
- });
- }
- } else {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
- dataInfo.transferId, targetPeerId, {
- name: dataInfo.name,
- size: dataInfo.size,
- percentage: 0,
- data: null,
- dataType: dataInfo.dataType,
- senderPeerId: self._user.sid,
- timeout: dataInfo.timeout,
- isPrivate: dataInfo.isPrivate
- },{
- message: message,
- transferType: self.DATA_TRANSFER_TYPE.UPLOAD
- });
- }
- };
-
- var startTransferFn = function (targetId, channel) {
- if (!hasSend) {
- hasSend = true;
- var payload = {
- type: self._DC_PROTOCOL_TYPE.WRQ,
- sender: self._user.sid,
- agent: window.webrtcDetectedBrowser,
- version: window.webrtcDetectedVersion,
- name: dataInfo.name,
- size: binarySize,
- dataType: dataInfo.dataType,
- chunkSize: binaryChunkSize,
- timeout: dataInfo.timeout,
- target: self._hasMCU ? targetPeerList : targetPeerId,
- isPrivate: dataInfo.isPrivate
- };
-
- if (self._hasMCU) {
- // if has MCU and is public, do not send individually
- self._sendDataChannelMessage('MCU', payload, channel);
- try {
- var mainChannel = self._dataChannels.MCU.main.label;
- self._setDataChannelTimeout('MCU', dataInfo.timeout, true, mainChannel);
- } catch (error) {
- log.error(['MCU', 'RTCDataChannel', 'MCU', 'Failed setting datachannel ' +
- 'timeout for MCU'], error);
- }
- } else {
- // if has MCU and is public, do not send individually
- self._sendDataChannelMessage(targetId, payload, channel);
- self._setDataChannelTimeout(targetId, dataInfo.timeout, true, channel);
- }
-
- }
- };
-
- log.log([targetPeerId, 'RTCDataChannel', targetChannel, 'Chunk size of data:'], {
- chunkSize: chunkSize,
- binaryChunkSize: binaryChunkSize,
- transferId: dataInfo.transferId,
- dataType: dataInfo.dataType
- });
-
-
- var supportMulti = false;
- var peerAgent = (self._peerInformations[targetPeerId] || {}).agent || {};
-
- if (!peerAgent && !peerAgent.name) {
- log.error([targetPeerId, 'RTCDataChannel', targetChannel, 'Aborting transfer to peer ' +
- 'as peer agent information for peer does not exists'], dataInfo);
- throwTransferErrorFn('Peer agent information for peer does not exists');
- return;
- }
-
- if (self._INTEROP_MULTI_TRANSFERS.indexOf(peerAgent.name) === -1) {
-
- targetChannel = targetPeerId + '-' + dataInfo.transferId;
- supportMulti = true;
-
- if (!(self._dataChannels[targetPeerId] || {}).main) {
- log.error([targetPeerId, 'RTCDataChannel', targetChannel,
- 'Main datachannel does not exists'], dataInfo);
- throwTransferErrorFn('Main datachannel does not exists');
- return;
-
- } else if (self._dataChannels[targetPeerId].main.readyState !==
- self.DATA_CHANNEL_STATE.OPEN) {
- log.error([targetPeerId, 'RTCDataChannel', targetChannel,
- 'Main datachannel is not opened'], {
- transferId: dataInfo.transferId,
- readyState: self._dataChannels[targetPeerId].main.readyState
- });
- throwTransferErrorFn('Main datachannel is not opened');
- return;
- }
-
- self._dataChannels[targetPeerId][targetChannel] =
- self._createDataChannel(targetPeerId, self.DATA_CHANNEL_TYPE.DATA, null, targetChannel);
-
- } else {
- var ongoingTransfer = null;
-
- if (self._uploadDataSessions[targetChannel]) {
- ongoingTransfer = self.DATA_TRANSFER_TYPE.UPLOAD;
- } else if (self._downloadDataSessions[targetChannel]) {
- ongoingTransfer = self.DATA_TRANSFER_TYPE.DOWNLOAD;
- }
-
- if (ongoingTransfer) {
- log.error([targetPeerId, 'RTCDataChannel', targetChannel, 'User have ongoing ' +
- ongoingTransfer + ' transfer session with peer. Unable to send data'], dataInfo);
- throwTransferErrorFn('Another ' + ongoingTransfer +
- ' transfer is ongoing. Unable to send data.');
- return;
- }
- }
-
- if (dataInfo.dataType === 'blob') {
- self._uploadDataTransfers[targetChannel] = self._chunkBlobData(data, chunkSize);
- } else {
- self._uploadDataTransfers[targetChannel] = self._chunkDataURL(data, chunkSize);
- }
-
- self._uploadDataSessions[targetChannel] = {
- name: dataInfo.name,
- size: binarySize,
- isUpload: true,
- senderPeerId: self._user.sid,
- transferId: dataInfo.transferId,
- percentage: 0,
- timeout: dataInfo.timeout,
- chunkSize: chunkSize,
- dataType: dataInfo.dataType,
- isPrivate: dataInfo.isPrivate
- };
-
- if (supportMulti) {
- self._condition('dataChannelState', function () {
- startTransferFn(targetPeerId, targetChannel);
- }, function () {
- return self._dataChannels[targetPeerId][targetChannel].readyState ===
- self.DATA_CHANNEL_STATE.OPEN;
- }, function (state) {
- return state === self.DATA_CHANNEL_STATE.OPEN;
- });
- } else {
- startTransferFn(targetChannel, targetChannel);
- }
-
- return targetChannel;
- };
-
- /**
- * Function that handles the data received from Datachannel and
- * routes to the relevant data transfer protocol handler.
- * @method _dataChannelProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._dataChannelProtocolHandler = function(dataString, peerId, channelName, channelType) {
- // PROTOCOL ESTABLISHMENT
-
- if (!(this._peerInformations[peerId] || {}).agent) {
- log.error([peerId, 'RTCDataChannel', channelName, 'Peer informations is missing during protocol ' +
- 'handling. Dropping packet'], dataString);
- return;
- }
-
- /*var useChannel = channelName;
- var peerAgent = this._peerInformations[peerId].agent.name;
-
- if (channelType === this.DATA_CHANNEL_TYPE.MESSAGING ||
- this._INTEROP_MULTI_TRANSFERS[peerAgent] > -1) {
- useChannel = peerId;
- }*/
-
- if (typeof dataString === 'string') {
- var data = {};
- try {
- data = JSON.parse(dataString);
- } catch (error) {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Received from peer ->'], {
- type: 'DATA',
- data: dataString
- });
- this._DATAProtocolHandler(peerId, dataString,
- this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING, channelName);
- return;
- }
- log.debug([peerId, 'RTCDataChannel', channelName, 'Received from peer ->'], {
- type: data.type,
- data: data
- });
- switch (data.type) {
- case this._DC_PROTOCOL_TYPE.WRQ:
- this._WRQProtocolHandler(peerId, data, channelName);
- break;
- case this._DC_PROTOCOL_TYPE.ACK:
- this._ACKProtocolHandler(peerId, data, channelName);
- break;
- case this._DC_PROTOCOL_TYPE.ERROR:
- this._ERRORProtocolHandler(peerId, data, channelName);
- break;
- case this._DC_PROTOCOL_TYPE.CANCEL:
- this._CANCELProtocolHandler(peerId, data, channelName);
- break;
- case this._DC_PROTOCOL_TYPE.MESSAGE: // Not considered a protocol actually?
- this._MESSAGEProtocolHandler(peerId, data, channelName);
- break;
- default:
- log.error([peerId, 'RTCDataChannel', channelName, 'Unsupported message ->'], {
- type: data.type,
- data: data
- });
- }
- }
- };
-
- /**
- * Function that handles the "WRQ" data transfer protocol.
- * @method _WRQProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._WRQProtocolHandler = function(peerId, data, channelName) {
- var transferId = channelName + this._TRANSFER_DELIMITER + (new Date()).getTime();
-
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Received file request from peer:'], data);
-
- var name = data.name;
- var binarySize = data.size;
- var expectedSize = data.chunkSize;
- var timeout = data.timeout;
-
- this._downloadDataSessions[channelName] = {
- transferId: transferId,
- name: name,
- isUpload: false,
- senderPeerId: peerId,
- size: binarySize,
- percentage: 0,
- dataType: data.dataType,
- ackN: 0,
- receivedSize: 0,
- chunkSize: expectedSize,
- timeout: timeout,
- isPrivate: data.isPrivate
- };
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.UPLOAD_REQUEST,
- transferId, peerId, {
- name: name,
- size: binarySize,
- percentage: 0,
- data: null,
- dataType: data.dataType,
- senderPeerId: peerId,
- timeout: timeout,
- isPrivate: data.isPrivate
- });
- this._trigger('incomingDataRequest', transferId, peerId, {
- name: name,
- size: binarySize,
- percentage: 0,
- dataType: data.dataType,
- senderPeerId: peerId,
- timeout: timeout,
- isPrivate: data.isPrivate
- }, false);
- };
-
- /**
- * Function that handles the "ACK" data transfer protocol.
- * @method _ACKProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._ACKProtocolHandler = function(peerId, data, channelName) {
- var self = this;
- var ackN = data.ackN;
- var transferStatus = self._uploadDataSessions[channelName];
-
- if (!transferStatus) {
- log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
- 'upload data transfers is empty'], {
- status: transferStatus,
- data: data
- });
- return;
- }
-
- if (!this._uploadDataTransfers[channelName]) {
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Ignoring data received as upload data transfers array is missing'], {
- data: data
- });
- return;
- }
-
- //peerId = (peerId === 'MCU') ? data.sender : peerId;
- var chunksLength = self._uploadDataTransfers[channelName].length;
- var transferId = transferStatus.transferId;
- var timeout = transferStatus.timeout;
-
- self._clearDataChannelTimeout(peerId, true, channelName);
- log.log([peerId, 'RTCDataChannel', channelName, 'ACK stage (' +
- transferStatus.transferId + ') ->'], ackN + ' / ' + chunksLength);
-
- if (ackN > -1) {
- // Still uploading
- if (ackN < chunksLength) {
- var sendDataFn = function (base64BinaryString) {
- var percentage = parseFloat((((ackN + 1) / chunksLength) * 100).toFixed(2), 10);
-
- if (!self._uploadDataSessions[channelName]) {
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Failed uploading as data session is empty'], {
- status: transferStatus,
- data: data
- });
- return;
- }
-
- self._uploadDataSessions[channelName].percentage = percentage;
-
- self._sendDataChannelMessage(peerId, base64BinaryString, channelName);
- self._setDataChannelTimeout(peerId, timeout, true, channelName);
-
- // to prevent from firing upload = 100;
- if (percentage !== 100) {
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOADING,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: percentage,
- data: null,
- dataType: transferStatus.dataType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- });
- }
- };
-
- if (transferStatus.dataType === 'blob') {
- self._blobToBase64(self._uploadDataTransfers[channelName][ackN], sendDataFn);
- } else {
- sendDataFn(self._uploadDataTransfers[channelName][ackN]);
- }
- } else if (ackN === chunksLength) {
- log.log([peerId, 'RTCDataChannel', channelName, 'Upload completed (' +
- transferStatus.transferId + ')'], transferStatus);
-
- self._trigger('dataTransferState',
- self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED, transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: 100,
- data: null,
- dataType: transferStatus.dataType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- });
-
- var blob = null;
-
- if (transferStatus.dataType === 'blob') {
- blob = new Blob(self._uploadDataTransfers[channelName]);
- } else {
- blob = self._assembleDataURL(self._uploadDataTransfers[channelName]);
- }
-
- self._trigger('incomingData', blob, transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: 100,
- dataType: transferStatus.dataType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, true);
- delete self._uploadDataTransfers[channelName];
- delete self._uploadDataSessions[channelName];
-
- // close datachannel after transfer
- if (self._dataChannels[peerId] && self._dataChannels[peerId][channelName]) {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for upload transfer']);
- self._closeDataChannel(peerId, channelName);
- }
- }
- } else {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Upload rejected (' +
- transferStatus.transferId + ')'], transferStatus);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.REJECTED,
- transferId, peerId, {
- name: transferStatus.name, //self._uploadDataSessions[channelName].name,
- size: transferStatus.size, //self._uploadDataSessions[channelName].size,
- percentage: 0,
- data: null,
- dataType: transferStatus.dataType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- });
- delete self._uploadDataTransfers[channelName];
- delete self._uploadDataSessions[channelName];
-
- // close datachannel if rejected
- if (self._dataChannels[peerId] && self._dataChannels[peerId][channelName]) {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for upload transfer']);
- self._closeDataChannel(peerId, channelName);
- }
- }
- };
-
- /**
- * Function that handles the "MESSAGE" data transfer protocol.
- * @method _MESSAGEProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._MESSAGEProtocolHandler = function(peerId, data, channelName) {
- var targetMid = data.sender;
- log.log([targetMid, 'RTCDataChannel', channelName,
- 'Received P2P message from peer:'], data);
- this._trigger('incomingMessage', {
- content: data.data,
- isPrivate: data.isPrivate,
- isDataChannel: true,
- targetPeerId: this._user.sid,
- senderPeerId: targetMid
- }, targetMid, this.getPeerInfo(targetMid), false);
- };
-
- /**
- * Function that handles the "ERROR" data transfer protocol.
- * @method _ERRORProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.2
- */
- Skylink.prototype._ERRORProtocolHandler = function(peerId, data, channelName) {
- var isUploader = data.isUploadError;
- var transferStatus = (isUploader) ? this._uploadDataSessions[channelName] :
- this._downloadDataSessions[channelName];
-
- if (!transferStatus) {
- log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
- (isUploader ? 'upload' : 'download') + ' data session is empty'], data);
- return;
- }
-
- var transferId = transferStatus.transferId;
-
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Received an error from peer:'], data);
- this._clearDataChannelTimeout(peerId, isUploader, channelName);
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: transferStatus.percentage,
- data: null,
- dataType: transferStatus.dataType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, {
- message: data.content,
- transferType: ((isUploader) ? this.DATA_TRANSFER_TYPE.UPLOAD :
- this.DATA_TRANSFER_TYPE.DOWNLOAD)
- });
- };
-
- /**
- * Function that handles the "CANCEL" data transfer protocol.
- * @method _CANCELProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.0
- */
- Skylink.prototype._CANCELProtocolHandler = function(peerId, data, channelName) {
- var isUpload = !!this._uploadDataSessions[channelName];
- var isDownload = !!this._downloadDataSessions[channelName];
- var transferStatus = (isUpload) ? this._uploadDataSessions[channelName] :
- this._downloadDataSessions[channelName];
-
- if (!transferStatus) {
- log.error([peerId, 'RTCDataChannel', channelName, 'Ignoring data received as ' +
- (isUpload ? 'upload' : 'download') + ' data session is empty'], data);
- return;
- }
-
- var transferId = transferStatus.transferId;
-
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Received file transfer cancel request:'], data);
-
- this._clearDataChannelTimeout(peerId, isUpload, channelName);
-
- try {
- if (isUpload) {
- delete this._uploadDataSessions[channelName];
- delete this._uploadDataTransfers[channelName];
- } else {
- delete this._downloadDataSessions[channelName];
- delete this._downloadDataTransfers[channelName];
- }
-
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.CANCEL,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- data: null,
- dataType: transferStatus.dataType,
- percentage: transferStatus.percentage,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, {
- message: data.content,
- transferType: ((isUpload) ? this.DATA_TRANSFER_TYPE.UPLOAD :
- this.DATA_TRANSFER_TYPE.DOWNLOAD)
- });
-
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Emptied file transfer session:'], data);
-
- } catch (error) {
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- data: null,
- dataType: transferStatus.dataType,
- percentage: transferStatus.percentage,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, {
- message: 'Failed cancelling data request from peer',
- transferType: ((isUpload) ? this.DATA_TRANSFER_TYPE.UPLOAD :
- this.DATA_TRANSFER_TYPE.DOWNLOAD)
- });
-
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Failed emptying file transfer session:'], {
- data: data,
- error: error
- });
- }
- };
-
- /**
- * Function that handles the data transfer chunk received.
- * @method _DATAProtocolHandler
- * @private
- * @for Skylink
- * @since 0.5.5
- */
- Skylink.prototype._DATAProtocolHandler = function(peerId, dataString, dataType, channelName) {
- var chunk, error = '';
- var transferStatus = this._downloadDataSessions[channelName];
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Received data chunk from peer ->'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA'
- });
-
- if (!transferStatus) {
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Ignoring data received as download data session is empty'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA'
- });
- return;
- }
-
- if (!this._downloadDataTransfers[channelName]) {
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Ignoring data received as download data transfers array is missing'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA'
- });
- return;
- }
-
- var transferId = transferStatus.transferId;
- var dataTransferType = transferStatus.dataType;
- var receivedSize = 0;
-
- this._clearDataChannelTimeout(peerId, false, channelName);
-
- if (dataType === this.DATA_TRANSFER_DATA_TYPE.BINARY_STRING) {
- if (dataTransferType === 'blob') {
- chunk = this._base64ToBlob(dataString);
- receivedSize = (chunk.size * (4 / 3));
- } else {
- chunk = dataString;
- receivedSize = dataString.length;
- }
- } else if (dataType === this.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER) {
- chunk = new Blob(dataString);
- } else if (dataType === this.DATA_TRANSFER_DATA_TYPE.BLOB) {
- chunk = dataString;
- } else {
- error = 'Unhandled data exception: ' + dataType;
- log.error([peerId, 'RTCDataChannel', channelName, 'Failed downloading data packets:'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA',
- error: error
- });
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.ERROR,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: transferStatus.percentage,
- data: null,
- dataType: dataTransferType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, {
- message: error,
- transferType: this.DATA_TRANSFER_TYPE.DOWNLOAD
- });
- return;
- }
-
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Received and expected data chunk size (' + receivedSize + ' === ' +
- transferStatus.chunkSize + ')'], {
- dataType: dataType,
- data: dataString,
- receivedSize: receivedSize,
- expectedSize: transferStatus.chunkSize,
- type: 'DATA'
- });
-
- if (transferStatus.chunkSize >= receivedSize) {
- this._downloadDataTransfers[channelName].push(chunk);
- transferStatus.ackN += 1;
- transferStatus.receivedSize += receivedSize;
- var totalReceivedSize = transferStatus.receivedSize;
- var percentage = parseFloat(((totalReceivedSize / transferStatus.size) * 100).toFixed(2), 10);
-
- this._sendDataChannelMessage(peerId, {
- type: this._DC_PROTOCOL_TYPE.ACK,
- sender: this._user.sid,
- ackN: transferStatus.ackN
- }, channelName);
-
- // update the percentage
- this._downloadDataSessions[channelName].percentage = percentage;
-
- if (transferStatus.chunkSize === receivedSize && percentage < 100) {
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Transfer in progress ACK n (' + transferStatus.ackN + ')'], {
- dataType: dataType,
- data: dataString,
- ackN: transferStatus.ackN,
- type: 'DATA'
- });
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOADING,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: percentage,
- data: null,
- dataType: dataTransferType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- });
- this._setDataChannelTimeout(peerId, transferStatus.timeout, false, channelName);
-
- if (!this._downloadDataSessions[channelName]) {
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Failed downloading as data session is empty'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA'
- });
- return;
- }
-
- this._downloadDataSessions[channelName].info = transferStatus;
-
- } else {
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Download complete'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA',
- transferInfo: transferStatus
- });
-
- var blob = null;
-
- if (dataTransferType === 'blob') {
- blob = new Blob(this._downloadDataTransfers[channelName]);
- } else {
- blob = this._assembleDataURL(this._downloadDataTransfers[channelName]);
- }
- this._trigger('dataTransferState', this.DATA_TRANSFER_STATE.DOWNLOAD_COMPLETED,
- transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: 100,
- data: blob,
- dataType: dataTransferType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- });
-
- this._trigger('incomingData', blob, transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: 100,
- dataType: dataTransferType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, false);
-
- delete this._downloadDataTransfers[channelName];
- delete this._downloadDataSessions[channelName];
-
- log.log([peerId, 'RTCDataChannel', channelName,
- 'Converted to Blob as download'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA',
- transferInfo: transferStatus
- });
-
- // close datachannel after transfer
- if (this._dataChannels[peerId] && this._dataChannels[peerId][channelName]) {
- log.debug([peerId, 'RTCDataChannel', channelName, 'Closing datachannel for download transfer']);
- this._closeDataChannel(peerId, channelName);
- }
- }
-
- } else {
- error = 'Packet not match - [Received]' + receivedSize +
- ' / [Expected]' + transferStatus.chunkSize;
-
- this._trigger('dataTransferState',
- this.DATA_TRANSFER_STATE.ERROR, transferId, peerId, {
- name: transferStatus.name,
- size: transferStatus.size,
- percentage: transferStatus.percentage,
- data: null,
- dataType: dataTransferType,
- senderPeerId: transferStatus.senderPeerId,
- timeout: transferStatus.timeout,
- isPrivate: transferStatus.isPrivate
- }, {
- message: error,
- transferType: this.DATA_TRANSFER_TYPE.DOWNLOAD
- });
-
- log.error([peerId, 'RTCDataChannel', channelName,
- 'Failed downloading data packets:'], {
- dataType: dataType,
- data: dataString,
- type: 'DATA',
- transferInfo: transferStatus,
- error: error
- });
- }
- };
-
- /**
- * Function that start the data transfer with the list of targeted Peer IDs provided.
- * At this stage, it will open a new Datachannel connection if simultaneous data transfers is
- * supported by Peer, or it will using the messaging type Datachannel connection.
- * Note that 1 data transfer can occur at a time in 1 Datachannel connection.
- * @method _startDataTransfer
- * @private
- * @for Skylink
- * @since 0.6.1
- */
- Skylink.prototype._startDataTransfer = function(data, dataInfo, listOfPeers, callback) {
- var self = this;
- var error = '';
- var noOfPeersSent = 0;
- var transferId = dataInfo.transferId;
- var dataType = dataInfo.dataType;
- var isPrivate = dataInfo.isPrivate;
- var i;
- var peerId;
-
- // for callback
- var listOfPeersTransferState = {};
- var transferSuccess = true;
- var listOfPeersTransferErrors = {};
- var listOfPeersChannels = {};
- var successfulPeerTransfers = [];
-
- var triggerCallbackFn = function () {
- for (i = 0; i < listOfPeers.length; i++) {
- var transferPeerId = listOfPeers[i];
-
- if (!listOfPeersTransferState[transferPeerId]) {
- // if error, make as false and break
- transferSuccess = false;
- break;
- }
- }
-
- if (transferSuccess) {
- log.log([null, 'RTCDataChannel', transferId, 'Firing success callback for data transfer'], dataInfo);
- // should we even support this? maybe keeping to not break older impl
- if (listOfPeers.length === 1 && isPrivate) {
- callback(null,{
- state: self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED,
- peerId: listOfPeers[0],
- listOfPeers: listOfPeers,
- transferId: transferId,
- isPrivate: isPrivate, // added new flag to indicate privacy
- transferInfo: dataInfo
- });
- } else {
- callback(null,{
- state: null,
- peerId: null,
- transferId: transferId,
- listOfPeers: listOfPeers,
- isPrivate: isPrivate, // added new flag to indicate privacy
- transferInfo: dataInfo
- });
- }
- } else {
- log.log([null, 'RTCDataChannel', transferId, 'Firing failure callback for data transfer'], dataInfo);
-
- // should we even support this? maybe keeping to not break older impl
- if (listOfPeers.length === 1 && isPrivate) {
- callback({
- state: self.DATA_TRANSFER_STATE.ERROR,
- error: listOfPeersTransferErrors[listOfPeers[0]],
- peerId: listOfPeers[0],
- transferId: transferId,
- transferErrors: listOfPeersTransferErrors,
- transferInfo: dataInfo,
- isPrivate: isPrivate, // added new flag to indicate privacy
- listOfPeers: listOfPeers
- }, null);
- } else {
- callback({
- state: null,
- peerId: null,
- error: null,
- transferId: transferId,
- listOfPeers: listOfPeers,
- isPrivate: isPrivate, // added new flag to indicate privacy
- transferInfo: dataInfo,
- transferErrors: listOfPeersTransferErrors
- }, null);
- }
- }
- };
-
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
-
- if (peerId === 'MCU') {
- continue;
- }
-
- if (self._dataChannels[peerId] && self._dataChannels[peerId].main) {
- log.log([peerId, 'RTCDataChannel', null, 'Sending blob data ->'], dataInfo);
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.UPLOAD_STARTED,
- transferId, peerId, {
- name: dataInfo.name,
- size: dataInfo.size,
- percentage: 0,
- data: data,
- dataType: dataType,
- senderPeerId: self._user.sid,
- timeout: dataInfo.timeout,
- isPrivate: isPrivate
- });
-
- self._trigger('incomingDataRequest', transferId, peerId, {
- name: dataInfo.name,
- size: dataInfo.size,
- percentage: 0,
- dataType: dataType,
- senderPeerId: self._user.sid,
- timeout: dataInfo.timeout,
- isPrivate: isPrivate
- }, true);
-
- if (!self._hasMCU) {
- listOfPeersChannels[peerId] =
- self._sendBlobDataToPeer(data, dataInfo, peerId);
- } else {
- listOfPeersChannels[peerId] = self._dataChannels[peerId].main.label;
- }
-
- noOfPeersSent++;
-
- } else {
- error = 'Datachannel does not exist. Unable to start data transfer with peer';
- log.error([peerId, 'RTCDataChannel', null, error]);
- listOfPeersTransferErrors[peerId] = error;
- }
- }
-
- // if has MCU
- if (self._hasMCU) {
- self._sendBlobDataToPeer(data, dataInfo, listOfPeers, isPrivate, transferId);
- }
-
- if (noOfPeersSent === 0) {
- error = 'Failed sending data as there is no available datachannels to send data';
-
- for (i = 0; i < listOfPeers.length; i++) {
- peerId = listOfPeers[i];
-
- self._trigger('dataTransferState', self.DATA_TRANSFER_STATE.ERROR,
- transferId, peerId, {
- name: dataInfo.name,
- size: dataInfo.size,
- data: null,
- dataType: dataType,
- percentage: 0,
- senderPeerId: self._user.sid,
- timeout: dataInfo.timeout,
- isPrivate: isPrivate
- }, {
- message: error,
- transferType: self.DATA_TRANSFER_TYPE.UPLOAD
- });
-
- listOfPeersTransferErrors[peerId] = error;
- }
-
- log.error([null, 'RTCDataChannel', null, error]);
- self._uploadDataTransfers = [];
- self._uploadDataSessions = [];
-
- transferSuccess = false;
-
- if (typeof callback === 'function') {
- triggerCallbackFn();
- }
- return;
- }
-
- if (typeof callback === 'function') {
- var dataChannelStateFn = function(state, transferringPeerId, errorObj, channelName, channelType){
- // check if error or closed halfway, if so abort
- if (state === self.DATA_CHANNEL_STATE.ERROR &&
- state === self.DATA_CHANNEL_STATE.CLOSED &&
- listOfPeersChannels[peerId] === channelName) {
- // if peer has already been inside, ignore
- if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
- listOfPeersTransferState[transferringPeerId] = false;
- listOfPeersTransferErrors[transferringPeerId] = errorObj;
-
- log.error([transferringPeerId, 'RTCDataChannel', null,
- 'Data channel state has met a failure state for peer (datachannel) ->'], {
- state: state,
- error: errorObj
- });
- }
- }
-
- if (Object.keys(listOfPeersTransferState).length === listOfPeers.length) {
- self.off('dataTransferState', dataTransferStateFn);
- self.off('dataChannelState', dataChannelStateFn);
-
- log.log([null, 'RTCDataChannel', transferId,
- 'Transfer states have been gathered completely in dataChannelState'], state);
-
- triggerCallbackFn();
- }
- };
-
- var dataTransferStateFn = function(state, stateTransferId, transferringPeerId, transferInfo, errorObj){
- // check if transfer is related to this transfer
- if (stateTransferId === transferId) {
- // check if state upload has completed
- if (state === self.DATA_TRANSFER_STATE.UPLOAD_COMPLETED) {
-
- log.debug([transferringPeerId, 'RTCDataChannel', stateTransferId,
- 'Data transfer state has met a success state for peer ->'], state);
-
- // if peer has already been inside, ignore
- if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
- listOfPeersTransferState[transferringPeerId] = true;
- }
- } else if(state === self.DATA_TRANSFER_STATE.REJECTED ||
- state === self.DATA_TRANSFER_STATE.CANCEL ||
- state === self.DATA_TRANSFER_STATE.ERROR) {
-
- if (state === self.DATA_TRANSFER_STATE.REJECTED) {
- errorObj = new Error('Peer has rejected data transfer request');
- }
-
- log.error([transferringPeerId, 'RTCDataChannel', stateTransferId,
- 'Data transfer state has met a failure state for peer ->'], {
- state: state,
- error: errorObj
- });
-
- // if peer has already been inside, ignore
- if (successfulPeerTransfers.indexOf(transferringPeerId) === -1) {
- listOfPeersTransferState[transferringPeerId] = false;
- listOfPeersTransferErrors[transferringPeerId] = errorObj;
- }
- }
- }
-
- if (Object.keys(listOfPeersTransferState).length === listOfPeers.length) {
- self.off('dataTransferState', dataTransferStateFn);
- self.off('dataChannelState', dataChannelStateFn);
-
- log.log([null, 'RTCDataChannel', stateTransferId,
- 'Transfer states have been gathered completely in dataTransferState'], state);
-
- triggerCallbackFn();
- }
- };
- self.on('dataTransferState', dataTransferStateFn);
- self.on('dataChannelState', dataChannelStateFn);
- }
- };
-
-