123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- /**
- * An abstraction on top of WebSockets and XHR streaming to provide fastest
- * possible connection for peers.
- */
- function Socket(server, id, key) {
- if (!(this instanceof Socket)) return new Socket(server, id, key);
- EventEmitter.call(this);
- this._id = id;
- this._server = server;
- this._httpUrl = 'http://' + this._server;
- this._key = key;
- };
- util.inherits(Socket, EventEmitter);
- /** Check in with ID or get one from server. */
- Socket.prototype._checkIn = function() {
- // If no ID provided, get a unique ID from server.
- var self = this;
- if (!this._id) {
- try {
- var http = new XMLHttpRequest();
- var url = this._httpUrl;
- // Set API key if necessary.
- if (!!this._key) {
- url += '/' + this._key;
- }
- url += '/id';
- // If there's no ID we need to wait for one before trying to init socket.
- http.open('get', url, true);
- http.onreadystatechange = function() {
- if (!self._id && http.readyState > 2 && !!http.responseText) {
- try {
- var response = JSON.parse(http.responseText.split('\n').shift());
- if (!!response.id) {
- self._id = response.id;
- self._startWebSocket();
- self.emit('message', { type: 'ID', id: self._id });
- }
- } catch (e) {
- self._startWebSocket();
- }
- }
- self._handleStream(http, true);
- };
- http.send(null);
- } catch(e) {
- util.log('XMLHttpRequest not available; defaulting to WebSockets');
- this._startWebSocket();
- }
- } else {
- this._startXhrStream();
- this._startWebSocket();
- }
- };
- /** Start up websocket communications. */
- Socket.prototype._startWebSocket = function() {
- if (!!this._socket) {
- return;
- }
- var wsurl = 'ws://' + this._server + '/ws';
- if (!!this._id) {
- wsurl += '?id=' + this._id;
- if (!!this._key) {
- wsurl += '&key=' + this._key;
- }
- } else if (!!this._key) {
- wsurl += '?key=' + this._key;
- }
- this._socket = new WebSocket(wsurl);
- var self = this;
- this._socket.onmessage = function(event) {
- var data;
- try {
- data = JSON.parse(event.data);
- } catch(e) {
- data = event.data;
- }
- if (data.constructor == Object) {
- self.emit('message', data);
- } else {
- util.log('Invalid server message', event.data);
- }
- };
- // Take care of the queue of connections if necessary and make sure Peer knows
- // socket is open.
- this._socket.onopen = function() {
- util.log('Socket open');
- if (self._id) {
- self.emit('open');
- }
- };
- };
- /** Start XHR streaming. */
- Socket.prototype._startXhrStream = function() {
- try {
- var self = this;
- var http = new XMLHttpRequest();
- var url = this._httpUrl;
- // Set API key if necessary.
- if (!!this._key) {
- url += '/' + this._key;
- }
- url += '/id';
- http.open('post', url, true);
- http.setRequestHeader('Content-Type', 'application/json');
- http.onreadystatechange = function() {
- self._handleStream(http);
- };
- http.send(JSON.stringify({ id: this._id }));
- } catch(e) {
- util.log('XMLHttpRequest not available; defaulting to WebSockets');
- }
- };
- /** Handles onreadystatechange response as a stream. */
- Socket.prototype._handleStream = function(http, pad) {
- // 3 and 4 are loading/done state. All others are not relevant.
- if (http.readyState < 3) {
- return;
- } else if (http.readyState == 3 && http.status != 200) {
- return;
- }
- if (this._index === undefined) {
- this._index = pad ? 2 : 1;
- }
-
- if (http.responseText === null) {
- return;
- }
-
- var message = http.responseText.split('\n')[this._index];
- if (!!message && http.readyState == 3) {
- this._index += 1;
- try {
- this._handleHTTPErrors(JSON.parse(message));
- } catch(e) {
- util.log('Invalid server message', message);
- }
- } else if (http.readyState == 4) {
- this._index = 1;
- }
- };
- Socket.prototype._handleHTTPErrors = function(message) {
- switch (message.type) {
- // XHR stream closed by timeout.
- case 'HTTP-END':
- util.log('XHR stream timed out.');
- if (!!this._socket && this._socket.readyState != 1) {
- this._startXhrStream();
- }
- break;
- // XHR stream closed by socket connect.
- case 'HTTP-SOCKET':
- util.log('XHR stream closed, WebSocket connected.');
- break;
- case 'HTTP-ERROR':
- // this.emit('error', 'Something went wrong.');
- util.log('XHR ended in error or the websocket connected first.');
- break;
- default:
- this.emit('message', message);
- }
- };
- /** Exposed send for DC & Peer. */
- Socket.prototype.send = function(data) {
- var type = data.type;
- message = JSON.stringify(data);
- if (!type) {
- this.emit('error', 'Invalid message');
- }
- if (!!this._socket && this._socket.readyState == 1) {
- this._socket.send(message);
- } else {
- var self = this;
- var http = new XMLHttpRequest();
- var url = this._httpUrl;
- // Set API key if necessary.
- if (!!this._key) {
- url += '/' + this._key;
- }
- url += '/' + type.toLowerCase();
-
- http.open('post', url, true);
- http.setRequestHeader('Content-Type', 'application/json');
- http.send(message);
- }
- };
- Socket.prototype.close = function() {
- if (!!this._socket && this._socket.readyState == 1) {
- this._socket.close();
- }
- };
- Socket.prototype.start = function() {
- this._checkIn();
- };
|