dataconnection.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /**
  2. * Wraps a DataChannel between two Peers.
  3. */
  4. function DataConnection(peer, provider, options) {
  5. if (!(this instanceof DataConnection)) return new DataConnection(peer, provider, options);
  6. EventEmitter.call(this);
  7. // TODO: perhaps default serialization should be binary-utf8?
  8. this.options = util.extend({
  9. serialization: 'binary',
  10. reliable: false
  11. }, options);
  12. // Connection is not open yet.
  13. this.open = false;
  14. this.type = 'data';
  15. this.peer = peer;
  16. this.provider = provider;
  17. this.id = this.options.connectionId || DataConnection._idPrefix + util.randomToken();
  18. this.label = this.options.label || this.id;
  19. this.metadata = this.options.metadata;
  20. this.serialization = this.options.serialization;
  21. this.reliable = this.options.reliable;
  22. // Data channel buffering.
  23. this._buffer = [];
  24. this._buffering = false;
  25. this.bufferSize = 0;
  26. // For storing large data.
  27. this._chunkedData = {};
  28. Negotiator.startConnection(
  29. this,
  30. this.options._payload || {
  31. originator: true
  32. }
  33. );
  34. }
  35. util.inherits(DataConnection, EventEmitter);
  36. DataConnection._idPrefix = 'dc_';
  37. /** Called by the Negotiator when the DataChannel is ready. */
  38. DataConnection.prototype.initialize = function(dc) {
  39. this._dc = this.dataChannel = dc;
  40. this._configureDataChannel();
  41. }
  42. DataConnection.prototype._configureDataChannel = function() {
  43. var self = this;
  44. if (util.supports.sctp) {
  45. this._dc.binaryType = 'arraybuffer';
  46. }
  47. this._dc.onopen = function() {
  48. util.log('Data channel connection success');
  49. self.open = true;
  50. self.emit('open');
  51. }
  52. // Use the Reliable shim for non Firefox browsers
  53. if (!util.supports.sctp && this.reliable) {
  54. this._reliable = new Reliable(this._dc, util.debug);
  55. }
  56. if (this._reliable) {
  57. this._reliable.onmessage = function(msg) {
  58. self.emit('data', msg);
  59. };
  60. } else {
  61. this._dc.onmessage = function(e) {
  62. self._handleDataMessage(e);
  63. };
  64. }
  65. this._dc.onclose = function(e) {
  66. util.log('DataChannel closed for:', self.peer);
  67. self.close();
  68. };
  69. }
  70. // Handles a DataChannel message.
  71. DataConnection.prototype._handleDataMessage = function(e) {
  72. var self = this;
  73. var data = e.data;
  74. var datatype = data.constructor;
  75. if (this.serialization === 'binary' || this.serialization === 'binary-utf8') {
  76. if (datatype === Blob) {
  77. // Datatype should never be blob
  78. util.blobToArrayBuffer(data, function(ab) {
  79. data = util.unpack(ab);
  80. self.emit('data', data);
  81. });
  82. return;
  83. } else if (datatype === ArrayBuffer) {
  84. data = util.unpack(data);
  85. } else if (datatype === String) {
  86. // String fallback for binary data for browsers that don't support binary yet
  87. var ab = util.binaryStringToArrayBuffer(data);
  88. data = util.unpack(ab);
  89. }
  90. } else if (this.serialization === 'json') {
  91. data = JSON.parse(data);
  92. }
  93. // Check if we've chunked--if so, piece things back together.
  94. // We're guaranteed that this isn't 0.
  95. if (data.__peerData) {
  96. var id = data.__peerData;
  97. var chunkInfo = this._chunkedData[id] || {data: [], count: 0, total: data.total};
  98. chunkInfo.data[data.n] = data.data;
  99. chunkInfo.count += 1;
  100. if (chunkInfo.total === chunkInfo.count) {
  101. // We've received all the chunks--time to construct the complete data.
  102. data = new Blob(chunkInfo.data);
  103. this._handleDataMessage({data: data});
  104. // We can also just delete the chunks now.
  105. delete this._chunkedData[id];
  106. }
  107. this._chunkedData[id] = chunkInfo;
  108. return;
  109. }
  110. this.emit('data', data);
  111. }
  112. /**
  113. * Exposed functionality for users.
  114. */
  115. /** Allows user to close connection. */
  116. DataConnection.prototype.close = function() {
  117. if (!this.open) {
  118. return;
  119. }
  120. this.open = false;
  121. Negotiator.cleanup(this);
  122. this.emit('close');
  123. }
  124. /** Allows user to send data. */
  125. DataConnection.prototype.send = function(data, chunked) {
  126. if (!this.open) {
  127. this.emit('error', new Error('Connection is not open. You should listen for the `open` event before sending messages.'));
  128. return;
  129. }
  130. if (this._reliable) {
  131. // Note: reliable shim sending will make it so that you cannot customize
  132. // serialization.
  133. this._reliable.send(data);
  134. return;
  135. }
  136. var self = this;
  137. if (this.serialization === 'json') {
  138. this._bufferedSend(JSON.stringify(data));
  139. } else if ('binary-utf8'.indexOf(this.serialization) !== -1) {
  140. var utf8 = (this.serialization === 'binary-utf8');
  141. var blob = util.pack(data, utf8);
  142. // For Chrome-Firefox interoperability, we need to make Firefox "chunking" the data it sends out. Future sophistication of this approach could make the decision on chunking dependent on the remote browser.
  143. if (/*util.browser !== 'Firefox' && */!chunked && blob.size > util.chunkedMTU) {
  144. this._sendChunks(blob);
  145. return;
  146. }
  147. // DataChannel currently only supports strings.
  148. if (!util.supports.sctp) {
  149. util.blobToBinaryString(blob, function(str) {
  150. self._bufferedSend(str);
  151. });
  152. } else if (!util.supports.binaryBlob) {
  153. // We only do this if we really need to (e.g. blobs are not supported),
  154. // because this conversion is costly.
  155. util.blobToArrayBuffer(blob, function(ab) {
  156. self._bufferedSend(ab);
  157. });
  158. } else {
  159. this._bufferedSend(blob);
  160. }
  161. } else {
  162. this._bufferedSend(data);
  163. }
  164. }
  165. DataConnection.prototype._bufferedSend = function(msg) {
  166. if (this._buffering || !this._trySend(msg)) {
  167. this._buffer.push(msg);
  168. this.bufferSize = this._buffer.length;
  169. }
  170. }
  171. // Returns true if the send succeeds.
  172. DataConnection.prototype._trySend = function(msg) {
  173. try {
  174. this._dc.send(msg);
  175. } catch (e) {
  176. this._buffering = true;
  177. var self = this;
  178. setTimeout(function() {
  179. // Try again.
  180. self._buffering = false;
  181. self._tryBuffer();
  182. }, 100);
  183. return false;
  184. }
  185. return true;
  186. }
  187. // Try to send the first message in the buffer.
  188. DataConnection.prototype._tryBuffer = function() {
  189. if (this._buffer.length === 0) {
  190. return;
  191. }
  192. var msg = this._buffer[0];
  193. if (this._trySend(msg)) {
  194. this._buffer.shift();
  195. this.bufferSize = this._buffer.length;
  196. this._tryBuffer();
  197. }
  198. }
  199. DataConnection.prototype._sendChunks = function(blob) {
  200. var blobs = util.chunk(blob);
  201. for (var i = 0, ii = blobs.length; i < ii; i += 1) {
  202. var blob = blobs[i];
  203. this.send(blob, true);
  204. }
  205. }
  206. DataConnection.prototype.handleMessage = function(message) {
  207. var payload = message.payload;
  208. switch (message.type) {
  209. case 'ANSWER':
  210. // Forward to negotiator
  211. Negotiator.handleSDP(message.type, this, payload.sdp);
  212. break;
  213. case 'CANDIDATE':
  214. Negotiator.handleCandidate(this, payload.candidate);
  215. break;
  216. default:
  217. util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer);
  218. break;
  219. }
  220. }