/** * 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' }, 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; // TODO: metadata could also be a part of the paylod. this.serialization = this.options.serialization; this.reliable = this.options.reliable; Negotiator.startConnection( this, this.options._payload || { originator: true, multiplex: this.options.multiplex // I don't think multiplex should be a // top-level property because it only // applies to the originator--otherwise // we'd just have an options.pc to use. } ); } 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; // TODO: util.supports.binary if (util.supports.binary) { // Webkit doesn't support binary yet 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 // TODO: util.supports.reliable if (!util.supports.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); // TODO: remove connection from Peer as well!! self.close(); }; } DataConnection.prototype._cleanup = function() { if (this._dc.readyState !== 'closing' && this._dc.readyState !== 'closed') { this._dc.close(); this.open = false; Negotiator.cleanup(this); this.emit('close'); } else { this.emit('error', new Error('The connection has already been closed')); } } // 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); } this.emit('data', data); } /** * Exposed functionality for users. */ /** Allows user to close connection. */ DataConnection.prototype.close = function() { if (!this.open) { return; } this._cleanup(); }; /** Allows user to send data. */ DataConnection.prototype.send = function(data) { if (!this.open) { this.emit('error', new Error('Connection is not open. You should listen for the `open` event before sending messages.')); } 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 === 'none') { this._dc.send(data); } else if (this.serialization === 'json') { this._dc.send(JSON.stringify(data)); } else { var utf8 = (this.serialization === 'binary-utf8'); var blob = util.pack(data, utf8); // DataChannel currently only supports strings. if (!util.supports.binary) { util.blobToBinaryString(blob, function(str){ self._dc.send(str); }); } else { this._dc.send(blob); } } }; DataConnection.prototype.handleMessage = function(message) { var payload = message.payload; switch (message.type) { case 'ANSWER': // TODO: assert sdp exists. // Should we pass `this`? // Forward to negotiator Negotiator.handleSDP(message.type, this, payload.sdp); break; case 'CANDIDATE': // TODO Negotiator.handleCandidate(this, payload.candidate); break; default: util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); break; } }