/** * An abstraction on top of WebSockets and XHR streaming to provide fastest * possible connection for peers. */ function Socket(host, port, key, id) { if (!(this instanceof Socket)) return new Socket(server, id, key); EventEmitter.call(this); this._id = id; var token = util.randomToken(); this._httpUrl = 'http://' + host + ':' + port + '/' + key + '/' + id + '/' + token; this._wsUrl = 'ws://' + host + ':' + port + '/peerjs?key='+key+'&id='+id+'&token='+token; }; util.inherits(Socket, EventEmitter); /** Check in with ID or get one from server. */ Socket.prototype.start = function() { this._startXhrStream(); this._startWebSocket(); }; /** Start up websocket communications. */ Socket.prototype._startWebSocket = function() { var self = this; if (!!this._socket) { return; } this._socket = new WebSocket(this._wsUrl); this._socket.onmessage = function(event) { var data; try { data = JSON.parse(event.data); } catch(e) { util.log('Invalid server message', event.data); return; } self.emit('message', data); }; // Take care of the queue of connections if necessary and make sure Peer knows // socket is open. this._socket.onopen = function() { if (!!self._timeout) { clearTimeout(self._timeout); setTimeout(function(){ self._http.abort(); self._http = null; }, 5000); } util.log('Socket open'); }; }; /** Start XHR streaming. */ Socket.prototype._startXhrStream = function(n) { try { var self = this; this._http = new XMLHttpRequest(); this._http._index = 1; this._http._streamIndex = n || 0; this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true); this._http.onreadystatechange = function() { if (this.readyState == 2 && !!this.old) { this.old.abort(); delete this.old; } if (this.readyState > 2 && this.status == 200 && !!this.responseText) { self._handleStream(this); } }; this._http.send(null); this._setHTTPTimeout(); } catch(e) { util.log('XMLHttpRequest not available; defaulting to WebSockets'); } }; /** Handles onreadystatechange response as a stream. */ Socket.prototype._handleStream = function(http) { var self = this; // 3 and 4 are loading/done state. All others are not relevant. var message = http.responseText.split('\n')[http._index]; if (!!message) { http._index += 1; try { message = JSON.parse(message); } catch(e) { util.log('Invalid server message', message); return; } self.emit('message', message); } }; Socket.prototype._setHTTPTimeout = function() { var self = this; this._timeout = setTimeout(function() { var old = self._http; if (!self._wsOpen()) { self._startXhrStream(old._streamIndex + 1); self._http.old = old; } else { old.abort(); } }, 25000); }; /** Exposed send for DC & Peer. */ Socket.prototype.send = function(data) { if (!data.type) { this.emit('error', 'Invalid message'); return; } var message = JSON.stringify(data); if (this._wsOpen()) { this._socket.send(message); } else { var http = new XMLHttpRequest(); var url = this._httpUrl + '/' + data.type.toLowerCase(); http.open('post', url, true); http.setRequestHeader('Content-Type', 'application/json'); http.send(message); } }; Socket.prototype.close = function() { if (!!this._wsOpen()) { this._socket.close(); } }; Socket.prototype._wsOpen = function() { return !!this._socket && this._socket.readyState == 1; };