diff --git a/lib/websocket-tracker.js b/lib/websocket-tracker.js index f7ba1f3..0e0966e 100644 --- a/lib/websocket-tracker.js +++ b/lib/websocket-tracker.js @@ -11,6 +11,11 @@ var Socket = require('simple-websocket') var common = require('./common') +// Use a socket pool, so tracker clients share WebSocket objects for the same server. +// In practice, WebSockets are pretty slow to establish, so this gives a nice performance +// boost, and saves browser resources. +var socketPool = {} + var RECONNECT_VARIANCE = 30 * 1000 var RECONNECT_MINIMUM = 5 * 1000 @@ -26,9 +31,10 @@ function WebSocketTracker (client, announceUrl, opts) { self._opts = opts + if (announceUrl[announceUrl.length - 1] === '/') { + announceUrl = announceUrl.substring(0, announceUrl.length - 1) + } self._announceUrl = announceUrl - if (self._announceUrl[self._announceUrl.length - 1] !== '/') self._announceUrl += '/' - self._announceUrl += '?' + hat(40) self._peers = {} // peers (offer id -> peer) self._socket = null @@ -102,7 +108,11 @@ WebSocketTracker.prototype._openSocket = function () { self._onSocketDataBound = self._onSocketData.bind(self) self._onSocketCloseBound = self._onSocketClose.bind(self) - self._socket = new Socket(self._announceUrl) + self._socket = socketPool[self._announceUrl] + if (!self._socket) { + self._socket = socketPool[self._announceUrl] = new Socket(self._announceUrl) + } + self._socket.on('data', self._onSocketDataBound) self._socket.on('close', self._onSocketCloseBound) self._socket.on('error', self._onSocketErrorBound) @@ -117,10 +127,22 @@ WebSocketTracker.prototype._onSocketData = function (data) { } if (data.info_hash !== self.client._infoHashBinary) { - return self.client.emit('warning', new Error('Invalid tracker response')) + debug( + 'ignoring websocket data from %s for %s (looking for %s: reused socket)', + self._announceUrl, common.binaryToHex(data.info_hash), self.client._infoHashHex + ) + return } - debug('received %s from %s', JSON.stringify(data), self._announceUrl) + if (data.peer_id && data.peer_id === self.client._peerIdBinary) { + // ignore offers/answers from this client + return + } + + debug( + 'received %s from %s for %s', + JSON.stringify(data), self._announceUrl, self.client._infoHashHex + ) var failure = data['failure reason'] if (failure) return self.client.emit('warning', new Error(failure)) @@ -149,13 +171,8 @@ WebSocketTracker.prototype._onSocketData = function (data) { }) } - if (self.client._peerIdHex === common.binaryToHex(data.peer_id)) { - // ignore offers/answers from this client - return - } - var peer - if (data.offer) { + if (data.offer && data.peer_id) { peer = new Peer({ trickle: false, config: self.client._rtcConfig, @@ -177,7 +194,7 @@ WebSocketTracker.prototype._onSocketData = function (data) { self.client.emit('peer', peer) } - if (data.answer) { + if (data.answer && data.peer_id) { peer = self._peers[common.binaryToHex(data.offer_id)] if (peer) { peer.id = common.binaryToHex(data.peer_id) @@ -191,18 +208,28 @@ WebSocketTracker.prototype._onSocketData = function (data) { WebSocketTracker.prototype._onSocketClose = function () { var self = this + if (self.destroyed) return self.destroy() - var ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + RECONNECT_MINIMUM - setTimeout(function () { - self._openSocket() - }, ms) - debug('reconnecting socket in %s ms', ms) + self._startReconnectTimer() } WebSocketTracker.prototype._onSocketError = function (err) { var self = this if (self.destroyed) return - self.client.emit('error', err) + self.destroy() + // errors will often happen if a tracker is offline, so don't treat it as fatal + self.client.emit('warning', err) + self._startReconnectTimer() +} + +WebSocketTracker.prototype._startReconnectTimer = function () { + var self = this + var ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + RECONNECT_MINIMUM + setTimeout(function () { + self.destroyed = false + self._openSocket() + }, ms) + debug('reconnecting socket in %s ms', ms) } WebSocketTracker.prototype._send = function (params) {