PageRenderTime 37ms CodeModel.GetById 13ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/socket.js

https://github.com/joker-2013/peerjs
JavaScript | 199 lines | 151 code | 27 blank | 21 comment | 29 complexity | c06accb3fbca2eec17e63fd5678c93a3 MD5 | raw file
  1/**
  2 * An abstraction on top of WebSockets and XHR streaming to provide fastest
  3 * possible connection for peers.
  4 */
  5function Socket(secure, host, port, path, key) {
  6  if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key);
  7
  8  EventEmitter.call(this);
  9
 10  // Disconnected manually.
 11  this.disconnected = false;
 12  this._queue = [];
 13
 14  var httpProtocol = secure ? 'https://' : 'http://';
 15  var wsProtocol = secure ? 'wss://' : 'ws://';
 16  this._httpUrl = httpProtocol + host + ':' + port + path + key;
 17  this._wsUrl = wsProtocol + host + ':' + port + path + 'peerjs?key=' + key;
 18}
 19
 20util.inherits(Socket, EventEmitter);
 21
 22
 23/** Check in with ID or get one from server. */
 24Socket.prototype.start = function(id, token) {
 25  this.id = id;
 26
 27  this._httpUrl += '/' + id + '/' + token;
 28  this._wsUrl += '&id='+id+'&token='+token;
 29
 30  this._startXhrStream();
 31  this._startWebSocket();
 32}
 33
 34
 35/** Start up websocket communications. */
 36Socket.prototype._startWebSocket = function(id) {
 37  var self = this;
 38
 39  if (this._socket) {
 40    return;
 41  }
 42
 43  this._socket = new WebSocket(this._wsUrl);
 44
 45  this._socket.onmessage = function(event) {
 46    var data;
 47    try {
 48      data = JSON.parse(event.data);
 49    } catch(e) {
 50      util.log('Invalid server message', event.data);
 51      return;
 52    }
 53    self.emit('message', data);
 54  };
 55
 56  // Take care of the queue of connections if necessary and make sure Peer knows
 57  // socket is open.
 58  this._socket.onopen = function() {
 59    if (self._timeout) {
 60      clearTimeout(self._timeout);
 61      setTimeout(function(){
 62        self._http.abort();
 63        self._http = null;
 64      }, 5000);
 65    }
 66    self._sendQueuedMessages();
 67    util.log('Socket open');
 68  };
 69}
 70
 71/** Start XHR streaming. */
 72Socket.prototype._startXhrStream = function(n) {
 73  try {
 74    var self = this;
 75    this._http = new XMLHttpRequest();
 76    this._http._index = 1;
 77    this._http._streamIndex = n || 0;
 78    this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true);
 79    this._http.onreadystatechange = function() {
 80      if (this.readyState == 2 && this.old) {
 81        this.old.abort();
 82        delete this.old;
 83      }
 84      if (this.readyState > 2 && this.status == 200 && this.responseText) {
 85        self._handleStream(this);
 86      }
 87    };
 88    this._http.send(null);
 89    this._setHTTPTimeout();
 90  } catch(e) {
 91    util.log('XMLHttpRequest not available; defaulting to WebSockets');
 92  }
 93}
 94
 95
 96/** Handles onreadystatechange response as a stream. */
 97Socket.prototype._handleStream = function(http) {
 98  // 3 and 4 are loading/done state. All others are not relevant.
 99  var messages = http.responseText.split('\n');
100
101  // Check to see if anything needs to be processed on buffer.
102  if (http._buffer) {
103    while (http._buffer.length > 0) {
104      var index = http._buffer.shift();
105      var bufferedMessage = messages[index];
106      try {
107        bufferedMessage = JSON.parse(bufferedMessage);
108      } catch(e) {
109        http._buffer.shift(index);
110        break;
111      }
112      this.emit('message', bufferedMessage);
113    }
114  }
115
116  var message = messages[http._index];
117  if (message) {
118    http._index += 1;
119    // Buffering--this message is incomplete and we'll get to it next time.
120    // This checks if the httpResponse ended in a `\n`, in which case the last
121    // element of messages should be the empty string.
122    if (http._index === messages.length) {
123      if (!http._buffer) {
124        http._buffer = [];
125      }
126      http._buffer.push(http._index - 1);
127    } else {
128      try {
129        message = JSON.parse(message);
130      } catch(e) {
131        util.log('Invalid server message', message);
132        return;
133      }
134      this.emit('message', message);
135    }
136  }
137}
138
139Socket.prototype._setHTTPTimeout = function() {
140  var self = this;
141  this._timeout = setTimeout(function() {
142    var old = self._http;
143    if (!self._wsOpen()) {
144      self._startXhrStream(old._streamIndex + 1);
145      self._http.old = old;
146    } else {
147      old.abort();
148    }
149  }, 25000);
150}
151
152/** Is the websocket currently open? */
153Socket.prototype._wsOpen = function() {
154  return this._socket && this._socket.readyState == 1;
155}
156
157/** Send queued messages. */
158Socket.prototype._sendQueuedMessages = function() {
159  for (var i = 0, ii = this._queue.length; i < ii; i += 1) {
160    this.send(this._queue[i]);
161  }
162}
163
164/** Exposed send for DC & Peer. */
165Socket.prototype.send = function(data) {
166  if (this.disconnected) {
167    return;
168  }
169
170  // If we didn't get an ID yet, we can't yet send anything so we should queue
171  // up these messages.
172  if (!this.id) {
173    this._queue.push(data);
174    return;
175  }
176
177  if (!data.type) {
178    this.emit('error', 'Invalid message');
179    return;
180  }
181
182  var message = JSON.stringify(data);
183  if (this._wsOpen()) {
184    this._socket.send(message);
185  } else {
186    var http = new XMLHttpRequest();
187    var url = this._httpUrl + '/' + data.type.toLowerCase();
188    http.open('post', url, true);
189    http.setRequestHeader('Content-Type', 'application/json');
190    http.send(message);
191  }
192}
193
194Socket.prototype.close = function() {
195  if (!this.disconnected && this._wsOpen()) {
196    this._socket.close();
197    this.disconnected = true;
198  }
199}