PageRenderTime 31ms CodeModel.GetById 16ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/socket.js

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