/** * Wraps a DataChannel between two Peers. */ function DataConnection(peer, provider, options) { if (!(this instanceof DataConnection)) return new DataConnection(peer, provider, options); EventEmitter.call(this); // TODO: perhaps default serialization should be binary-utf8? this.options = util.extend({ serialization: 'binary', reliable: false }, options); // Connection is not open yet. this.open = false; this.type = 'data'; this.peer = peer; this.provider = provider; this.id = this.options.connectionId || DataConnection._idPrefix + util.randomToken(); this.label = this.options.label || this.id; this.metadata = this.options.metadata; this.serialization = this.options.serialization; this.reliable = this.options.reliable; // For storing large data. this._chunkedData = {}; Negotiator.startConnection( this, this.options._payload || { originator: true } ); } util.inherits(DataConnection, EventEmitter); DataConnection._idPrefix = 'dc_'; /** Called by the Negotiator when the DataChannel is ready. */ DataConnection.prototype.initialize = function(dc) { this._dc = dc; this._configureDataChannel(); } DataConnection.prototype._configureDataChannel = function() { var self = this; if (util.supports.sctp) { this._dc.binaryType = 'arraybuffer'; } this._dc.onopen = function() { util.log('Data channel connection success'); self.open = true; self.emit('open'); } // Use the Reliable shim for non Firefox browsers if (!util.supports.sctp && this.reliable) { this._reliable = new Reliable(this._dc, util.debug); } if (this._reliable) { this._reliable.onmessage = function(msg) { self.emit('data', msg); }; } else { this._dc.onmessage = function(e) { self._handleDataMessage(e); }; } this._dc.onclose = function(e) { util.log('DataChannel closed for:', self.peer); self.close(); }; } // Handles a DataChannel message. DataConnection.prototype._handleDataMessage = function(e) { var self = this; var data = e.data; var datatype = data.constructor; if (this.serialization === 'binary' || this.serialization === 'binary-utf8') { if (datatype === Blob) { // Datatype should never be blob util.blobToArrayBuffer(data, function(ab) { data = util.unpack(ab); self.emit('data', data); }); return; } else if (datatype === ArrayBuffer) { data = util.unpack(data); } else if (datatype === String) { // String fallback for binary data for browsers that don't support binary yet var ab = util.binaryStringToArrayBuffer(data); data = util.unpack(ab); } } else if (this.serialization === 'json') { data = JSON.parse(data); } // Check if we've chunked--if so, piece things back together. // We're guaranteed that this isn't 0. if (data.__peerData) { var id = data.__peerData; var chunkInfo = this._chunkedData[id] || {data: [], count: 0, total: data.total}; chunkInfo.data[data.n] = data.data; chunkInfo.count += 1; if (chunkInfo.total === chunkInfo.count) { // We've received all the chunks--time to construct the complete data. data = new Blob(chunkInfo.data); this._handleDataMessage({data: data}); // We can also just delete the chunks now. delete this._chunkedData[id]; } this._chunkedData[id] = chunkInfo; return; } this.emit('data', data); } /** * Exposed functionality for users. */ /** Allows user to close connection. */ DataConnection.prototype.close = function() { if (!this.open) { return; } this.open = false; Negotiator.cleanup(this); this.emit('close'); } /** Allows user to send data. */ DataConnection.prototype.send = function(data, chunked) { if (!this.open) { this.emit('error', new Error('Connection is not open. You should listen for the `open` event before sending messages.')); return; } if (this._reliable) { // Note: reliable shim sending will make it so that you cannot customize // serialization. this._reliable.send(data); return; } var self = this; if (this.serialization === 'json') { this._dc.send(JSON.stringify(data)); } else if ('binary-utf8'.indexOf(this.serialization) !== -1) { var utf8 = (this.serialization === 'binary-utf8'); var blob = util.pack(data, utf8); if (util.browser !== 'Firefox' && !chunked && blob.size > util.chunkedMTU) { this._sendChunks(blob); return; } // DataChannel currently only supports strings. if (!util.supports.sctp) { util.blobToBinaryString(blob, function(str) { self._dc.send(str); }); } else if (!util.supports.binaryBlob) { // We only do this if we really need to (e.g. blobs are not supported), // because this conversion is costly. util.blobToArrayBuffer(blob, function(ab) { self._dc.send(ab); }); } else { this._dc.send(blob); } } else { this._dc.send(data); } } DataConnection.prototype._sendChunks = function(blob) { var blobs = util.chunk(blob); for (var i = 0, ii = blobs.length; i < ii; i += 1) { var blob = blobs[i]; this.send(blob, true); } } DataConnection.prototype.handleMessage = function(message) { var payload = message.payload; switch (message.type) { case 'ANSWER': // Forward to negotiator Negotiator.handleSDP(message.type, this, payload.sdp); break; case 'CANDIDATE': Negotiator.handleCandidate(this, payload.candidate); break; default: util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); break; } }