diff --git a/lib/client/http-tracker.js b/lib/client/http-tracker.js index 656d96d..5f14a37 100644 --- a/lib/client/http-tracker.js +++ b/lib/client/http-tracker.js @@ -20,47 +20,44 @@ class HTTPTracker extends Tracker { constructor (client, announceUrl, opts) { super(client, announceUrl) - const self = this debug('new http tracker %s', announceUrl) // Determine scrape url (if http tracker supports it) - self.scrapeUrl = null + this.scrapeUrl = null - const match = self.announceUrl.match(HTTP_SCRAPE_SUPPORT) + const match = this.announceUrl.match(HTTP_SCRAPE_SUPPORT) if (match) { - const pre = self.announceUrl.slice(0, match.index) - const post = self.announceUrl.slice(match.index + 9) - self.scrapeUrl = `${pre}/scrape${post}` + const pre = this.announceUrl.slice(0, match.index) + const post = this.announceUrl.slice(match.index + 9) + this.scrapeUrl = `${pre}/scrape${post}` } - self.cleanupFns = [] - self.maybeDestroyCleanup = null + this.cleanupFns = [] + this.maybeDestroyCleanup = null } announce (opts) { - const self = this - if (self.destroyed) return + if (this.destroyed) return const params = Object.assign({}, opts, { compact: (opts.compact == null) ? 1 : opts.compact, - info_hash: self.client._infoHashBinary, - peer_id: self.client._peerIdBinary, - port: self.client._port + info_hash: this.client._infoHashBinary, + peer_id: this.client._peerIdBinary, + port: this.client._port }) - if (self._trackerId) params.trackerid = self._trackerId + if (this._trackerId) params.trackerid = this._trackerId - self._request(self.announceUrl, params, (err, data) => { - if (err) return self.client.emit('warning', err) - self._onAnnounceResponse(data) + this._request(this.announceUrl, params, (err, data) => { + if (err) return this.client.emit('warning', err) + this._onAnnounceResponse(data) }) } scrape (opts) { - const self = this - if (self.destroyed) return + if (this.destroyed) return - if (!self.scrapeUrl) { - self.client.emit('error', new Error(`scrape not supported ${self.announceUrl}`)) + if (!this.scrapeUrl) { + this.client.emit('error', new Error(`scrape not supported ${this.announceUrl}`)) return } @@ -68,24 +65,24 @@ class HTTPTracker extends Tracker { ? opts.infoHash.map(infoHash => { return infoHash.toString('binary') }) - : (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary + : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary const params = { info_hash: infoHashes } - self._request(self.scrapeUrl, params, (err, data) => { - if (err) return self.client.emit('warning', err) - self._onScrapeResponse(data) + this._request(this.scrapeUrl, params, (err, data) => { + if (err) return this.client.emit('warning', err) + this._onScrapeResponse(data) }) } destroy (cb) { const self = this - if (self.destroyed) return cb(null) - self.destroyed = true - clearInterval(self.interval) + if (this.destroyed) return cb(null) + this.destroyed = true + clearInterval(this.interval) // If there are no pending requests, destroy immediately. - if (self.cleanupFns.length === 0) return destroyCleanup() + if (this.cleanupFns.length === 0) return destroyCleanup() // Otherwise, wait a short time for pending requests to complete, then force // destroy them. @@ -93,8 +90,8 @@ class HTTPTracker extends Tracker { // But, if all pending requests complete before the timeout fires, do cleanup // right away. - self.maybeDestroyCleanup = () => { - if (self.cleanupFns.length === 0) destroyCleanup() + this.maybeDestroyCleanup = () => { + if (this.cleanupFns.length === 0) destroyCleanup() } function destroyCleanup () { @@ -116,13 +113,13 @@ class HTTPTracker extends Tracker { const u = requestUrl + (!requestUrl.includes('?') ? '?' : '&') + common.querystringStringify(params) - self.cleanupFns.push(cleanup) + this.cleanupFns.push(cleanup) let request = get.concat({ url: u, timeout: common.REQUEST_TIMEOUT, headers: { - 'user-agent': self.client._userAgent || '' + 'user-agent': this.client._userAgent || '' } }, onResponse) @@ -171,22 +168,20 @@ class HTTPTracker extends Tracker { } _onAnnounceResponse (data) { - const self = this - const interval = data.interval || data['min interval'] - if (interval) self.setInterval(interval * 1000) + if (interval) this.setInterval(interval * 1000) const trackerId = data['tracker id'] if (trackerId) { // If absent, do not discard previous trackerId value - self._trackerId = trackerId + this._trackerId = trackerId } const response = Object.assign({}, data, { - announce: self.announceUrl, + announce: this.announceUrl, infoHash: common.binaryToHex(data.info_hash) }) - self.client.emit('update', response) + this.client.emit('update', response) let addrs if (Buffer.isBuffer(data.peers)) { @@ -194,15 +189,15 @@ class HTTPTracker extends Tracker { try { addrs = compact2string.multi(data.peers) } catch (err) { - return self.client.emit('warning', err) + return this.client.emit('warning', err) } addrs.forEach(addr => { - self.client.emit('peer', addr) + this.client.emit('peer', addr) }) } else if (Array.isArray(data.peers)) { // tracker returned normal response data.peers.forEach(peer => { - self.client.emit('peer', `${peer.ip}:${peer.port}`) + this.client.emit('peer', `${peer.ip}:${peer.port}`) }) } @@ -211,10 +206,10 @@ class HTTPTracker extends Tracker { try { addrs = compact2string.multi6(data.peers6) } catch (err) { - return self.client.emit('warning', err) + return this.client.emit('warning', err) } addrs.forEach(addr => { - self.client.emit('peer', addr) + this.client.emit('peer', addr) }) } else if (Array.isArray(data.peers6)) { // tracker returned normal response @@ -222,20 +217,19 @@ class HTTPTracker extends Tracker { const ip = /^\[/.test(peer.ip) || !/:/.test(peer.ip) ? peer.ip /* ipv6 w/ brackets or domain name */ : `[${peer.ip}]` /* ipv6 without brackets */ - self.client.emit('peer', `${ip}:${peer.port}`) + this.client.emit('peer', `${ip}:${peer.port}`) }) } } _onScrapeResponse (data) { - const self = this // NOTE: the unofficial spec says to use the 'files' key, 'host' has been // seen in practice data = data.files || data.host || {} const keys = Object.keys(data) if (keys.length === 0) { - self.client.emit('warning', new Error('invalid scrape response')) + this.client.emit('warning', new Error('invalid scrape response')) return } @@ -243,10 +237,10 @@ class HTTPTracker extends Tracker { // TODO: optionally handle data.flags.min_request_interval // (separate from announce interval) const response = Object.assign(data[infoHash], { - announce: self.announceUrl, + announce: this.announceUrl, infoHash: common.binaryToHex(infoHash) }) - self.client.emit('scrape', response) + this.client.emit('scrape', response) }) } } diff --git a/lib/client/tracker.js b/lib/client/tracker.js index cd3175e..cbbd23d 100644 --- a/lib/client/tracker.js +++ b/lib/client/tracker.js @@ -4,25 +4,23 @@ class Tracker extends EventEmitter { constructor (client, announceUrl) { super() - const self = this - self.client = client - self.announceUrl = announceUrl + this.client = client + this.announceUrl = announceUrl - self.interval = null - self.destroyed = false + this.interval = null + this.destroyed = false } setInterval (intervalMs) { - const self = this - if (intervalMs == null) intervalMs = self.DEFAULT_ANNOUNCE_INTERVAL + if (intervalMs == null) intervalMs = this.DEFAULT_ANNOUNCE_INTERVAL - clearInterval(self.interval) + clearInterval(this.interval) if (intervalMs) { - self.interval = setInterval(() => { - self.announce(self.client._defaultAnnounceOpts()) + this.interval = setInterval(() => { + this.announce(this.client._defaultAnnounceOpts()) }, intervalMs) - if (self.interval.unref) self.interval.unref() + if (this.interval.unref) this.interval.unref() } } } diff --git a/lib/client/udp-tracker.js b/lib/client/udp-tracker.js index 7d0f901..239ae12 100644 --- a/lib/client/udp-tracker.js +++ b/lib/client/udp-tracker.js @@ -20,34 +20,31 @@ const Tracker = require('./tracker') class UDPTracker extends Tracker { constructor (client, announceUrl, opts) { super(client, announceUrl) - const self = this debug('new udp tracker %s', announceUrl) - self.cleanupFns = [] - self.maybeDestroyCleanup = null + this.cleanupFns = [] + this.maybeDestroyCleanup = null } announce (opts) { - const self = this - if (self.destroyed) return - self._request(opts) + if (this.destroyed) return + this._request(opts) } scrape (opts) { - const self = this - if (self.destroyed) return + if (this.destroyed) return opts._scrape = true - self._request(opts) // udp scrape uses same announce url + this._request(opts) // udp scrape uses same announce url } destroy (cb) { const self = this - if (self.destroyed) return cb(null) - self.destroyed = true - clearInterval(self.interval) + if (this.destroyed) return cb(null) + this.destroyed = true + clearInterval(this.interval) // If there are no pending requests, destroy immediately. - if (self.cleanupFns.length === 0) return destroyCleanup() + if (this.cleanupFns.length === 0) return destroyCleanup() // Otherwise, wait a short time for pending requests to complete, then force // destroy them. @@ -55,8 +52,8 @@ class UDPTracker extends Tracker { // But, if all pending requests complete before the timeout fires, do cleanup // right away. - self.maybeDestroyCleanup = () => { - if (self.cleanupFns.length === 0) destroyCleanup() + this.maybeDestroyCleanup = () => { + if (this.cleanupFns.length === 0) destroyCleanup() } function destroyCleanup () { @@ -76,7 +73,7 @@ class UDPTracker extends Tracker { _request (opts) { const self = this if (!opts) opts = {} - const parsedUrl = url.parse(self.announceUrl) + const parsedUrl = url.parse(this.announceUrl) let transactionId = genTransactionId() let socket = dgram.createSocket('udp4') @@ -88,7 +85,7 @@ class UDPTracker extends Tracker { }, common.REQUEST_TIMEOUT) if (timeout.unref) timeout.unref() - self.cleanupFns.push(cleanup) + this.cleanupFns.push(cleanup) send(Buffer.concat([ common.CONNECTION_ID, diff --git a/lib/client/websocket-tracker.js b/lib/client/websocket-tracker.js index f0294a0..70e8d4e 100644 --- a/lib/client/websocket-tracker.js +++ b/lib/client/websocket-tracker.js @@ -19,61 +19,58 @@ const OFFER_TIMEOUT = 50 * 1000 class WebSocketTracker extends Tracker { constructor (client, announceUrl, opts) { super(client, announceUrl) - const self = this debug('new websocket tracker %s', announceUrl) - self.peers = {} // peers (offer id -> peer) - self.socket = null + this.peers = {} // peers (offer id -> peer) + this.socket = null - self.reconnecting = false - self.retries = 0 - self.reconnectTimer = null + this.reconnecting = false + this.retries = 0 + this.reconnectTimer = null // Simple boolean flag to track whether the socket has received data from // the websocket server since the last time socket.send() was called. - self.expectingResponse = false + this.expectingResponse = false - self._openSocket() + this._openSocket() } announce (opts) { - const self = this - if (self.destroyed || self.reconnecting) return - if (!self.socket.connected) { - self.socket.once('connect', () => { - self.announce(opts) + if (this.destroyed || this.reconnecting) return + if (!this.socket.connected) { + this.socket.once('connect', () => { + this.announce(opts) }) return } const params = Object.assign({}, opts, { action: 'announce', - info_hash: self.client._infoHashBinary, - peer_id: self.client._peerIdBinary + info_hash: this.client._infoHashBinary, + peer_id: this.client._peerIdBinary }) - if (self._trackerId) params.trackerid = self._trackerId + if (this._trackerId) params.trackerid = this._trackerId if (opts.event === 'stopped' || opts.event === 'completed') { // Don't include offers with 'stopped' or 'completed' event - self._send(params) + this._send(params) } else { // Limit the number of offers that are generated, since it can be slow const numwant = Math.min(opts.numwant, 10) - self._generateOffers(numwant, offers => { + this._generateOffers(numwant, offers => { params.numwant = numwant params.offers = offers - self._send(params) + this._send(params) }) } } scrape (opts) { - const self = this - if (self.destroyed || self.reconnecting) return - if (!self.socket.connected) { - self.socket.once('connect', () => { - self.scrape(opts) + if (this.destroyed || this.reconnecting) return + if (!this.socket.connected) { + this.socket.once('connect', () => { + this.scrape(opts) }) return } @@ -82,60 +79,58 @@ class WebSocketTracker extends Tracker { ? opts.infoHash.map(infoHash => { return infoHash.toString('binary') }) - : (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary + : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary const params = { action: 'scrape', info_hash: infoHashes } - self._send(params) + this._send(params) } - destroy (cb) { - const self = this - if (!cb) cb = noop - if (self.destroyed) return cb(null) + destroy (cb = noop) { + if (this.destroyed) return cb(null) - self.destroyed = true + this.destroyed = true - clearInterval(self.interval) - clearTimeout(self.reconnectTimer) + clearInterval(this.interval) + clearTimeout(this.reconnectTimer) // Destroy peers - for (const peerId in self.peers) { - const peer = self.peers[peerId] + for (const peerId in this.peers) { + const peer = this.peers[peerId] clearTimeout(peer.trackerTimeout) peer.destroy() } - self.peers = null + this.peers = null - if (self.socket) { - self.socket.removeListener('connect', self._onSocketConnectBound) - self.socket.removeListener('data', self._onSocketDataBound) - self.socket.removeListener('close', self._onSocketCloseBound) - self.socket.removeListener('error', self._onSocketErrorBound) - self.socket = null + if (this.socket) { + this.socket.removeListener('connect', this._onSocketConnectBound) + this.socket.removeListener('data', this._onSocketDataBound) + this.socket.removeListener('close', this._onSocketCloseBound) + this.socket.removeListener('error', this._onSocketErrorBound) + this.socket = null } - self._onSocketConnectBound = null - self._onSocketErrorBound = null - self._onSocketDataBound = null - self._onSocketCloseBound = null + this._onSocketConnectBound = null + this._onSocketErrorBound = null + this._onSocketDataBound = null + this._onSocketCloseBound = null - if (socketPool[self.announceUrl]) { - socketPool[self.announceUrl].consumers -= 1 + if (socketPool[this.announceUrl]) { + socketPool[this.announceUrl].consumers -= 1 } // Other instances are using the socket, so there's nothing left to do here - if (socketPool[self.announceUrl].consumers > 0) return cb() + if (socketPool[this.announceUrl].consumers > 0) return cb() - let socket = socketPool[self.announceUrl] - delete socketPool[self.announceUrl] + let socket = socketPool[this.announceUrl] + delete socketPool[this.announceUrl] socket.on('error', noop) // ignore all future errors socket.once('close', cb) // If there is no data response expected, destroy immediately. - if (!self.expectingResponse) return destroyCleanup() + if (!this.expectingResponse) return destroyCleanup() // Otherwise, wait a short time for potential responses to come in from the // server, then force close the socket. @@ -157,147 +152,142 @@ class WebSocketTracker extends Tracker { } _openSocket () { - const self = this - self.destroyed = false + this.destroyed = false - if (!self.peers) self.peers = {} + if (!this.peers) this.peers = {} - self._onSocketConnectBound = () => { - self._onSocketConnect() + this._onSocketConnectBound = () => { + this._onSocketConnect() } - self._onSocketErrorBound = err => { - self._onSocketError(err) + this._onSocketErrorBound = err => { + this._onSocketError(err) } - self._onSocketDataBound = data => { - self._onSocketData(data) + this._onSocketDataBound = data => { + this._onSocketData(data) } - self._onSocketCloseBound = () => { - self._onSocketClose() + this._onSocketCloseBound = () => { + this._onSocketClose() } - self.socket = socketPool[self.announceUrl] - if (self.socket) { - socketPool[self.announceUrl].consumers += 1 + this.socket = socketPool[this.announceUrl] + if (this.socket) { + socketPool[this.announceUrl].consumers += 1 } else { - self.socket = socketPool[self.announceUrl] = new Socket(self.announceUrl) - self.socket.consumers = 1 - self.socket.once('connect', self._onSocketConnectBound) + this.socket = socketPool[this.announceUrl] = new Socket(this.announceUrl) + this.socket.consumers = 1 + this.socket.once('connect', this._onSocketConnectBound) } - self.socket.on('data', self._onSocketDataBound) - self.socket.once('close', self._onSocketCloseBound) - self.socket.once('error', self._onSocketErrorBound) + this.socket.on('data', this._onSocketDataBound) + this.socket.once('close', this._onSocketCloseBound) + this.socket.once('error', this._onSocketErrorBound) } _onSocketConnect () { - const self = this - if (self.destroyed) return + if (this.destroyed) return - if (self.reconnecting) { - self.reconnecting = false - self.retries = 0 - self.announce(self.client._defaultAnnounceOpts()) + if (this.reconnecting) { + this.reconnecting = false + this.retries = 0 + this.announce(this.client._defaultAnnounceOpts()) } } _onSocketData (data) { - const self = this - if (self.destroyed) return + if (this.destroyed) return - self.expectingResponse = false + this.expectingResponse = false try { data = JSON.parse(data) } catch (err) { - self.client.emit('warning', new Error('Invalid tracker response')) + this.client.emit('warning', new Error('Invalid tracker response')) return } if (data.action === 'announce') { - self._onAnnounceResponse(data) + this._onAnnounceResponse(data) } else if (data.action === 'scrape') { - self._onScrapeResponse(data) + this._onScrapeResponse(data) } else { - self._onSocketError(new Error(`invalid action in WS response: ${data.action}`)) + this._onSocketError(new Error(`invalid action in WS response: ${data.action}`)) } } _onAnnounceResponse (data) { - const self = this - - if (data.info_hash !== self.client._infoHashBinary) { + if (data.info_hash !== this.client._infoHashBinary) { debug( 'ignoring websocket data from %s for %s (looking for %s: reused socket)', - self.announceUrl, common.binaryToHex(data.info_hash), self.client.infoHash + this.announceUrl, common.binaryToHex(data.info_hash), this.client.infoHash ) return } - if (data.peer_id && data.peer_id === self.client._peerIdBinary) { + if (data.peer_id && data.peer_id === this.client._peerIdBinary) { // ignore offers/answers from this client return } debug( 'received %s from %s for %s', - JSON.stringify(data), self.announceUrl, self.client.infoHash + JSON.stringify(data), this.announceUrl, this.client.infoHash ) const failure = data['failure reason'] - if (failure) return self.client.emit('warning', new Error(failure)) + if (failure) return this.client.emit('warning', new Error(failure)) const warning = data['warning message'] - if (warning) self.client.emit('warning', new Error(warning)) + if (warning) this.client.emit('warning', new Error(warning)) const interval = data.interval || data['min interval'] - if (interval) self.setInterval(interval * 1000) + if (interval) this.setInterval(interval * 1000) const trackerId = data['tracker id'] if (trackerId) { // If absent, do not discard previous trackerId value - self._trackerId = trackerId + this._trackerId = trackerId } if (data.complete != null) { const response = Object.assign({}, data, { - announce: self.announceUrl, + announce: this.announceUrl, infoHash: common.binaryToHex(data.info_hash) }) - self.client.emit('update', response) + this.client.emit('update', response) } let peer if (data.offer && data.peer_id) { debug('creating peer (from remote offer)') - peer = self._createPeer() + peer = this._createPeer() peer.id = common.binaryToHex(data.peer_id) peer.once('signal', answer => { const params = { action: 'announce', - info_hash: self.client._infoHashBinary, - peer_id: self.client._peerIdBinary, + info_hash: this.client._infoHashBinary, + peer_id: this.client._peerIdBinary, to_peer_id: data.peer_id, answer, offer_id: data.offer_id } - if (self._trackerId) params.trackerid = self._trackerId - self._send(params) + if (this._trackerId) params.trackerid = this._trackerId + this._send(params) }) peer.signal(data.offer) - self.client.emit('peer', peer) + this.client.emit('peer', peer) } if (data.answer && data.peer_id) { const offerId = common.binaryToHex(data.offer_id) - peer = self.peers[offerId] + peer = this.peers[offerId] if (peer) { peer.id = common.binaryToHex(data.peer_id) peer.signal(data.answer) - self.client.emit('peer', peer) + this.client.emit('peer', peer) clearTimeout(peer.trackerTimeout) peer.trackerTimeout = null - delete self.peers[offerId] + delete this.peers[offerId] } else { debug(`got unexpected answer: ${JSON.stringify(data.answer)}`) } @@ -305,12 +295,11 @@ class WebSocketTracker extends Tracker { } _onScrapeResponse (data) { - const self = this data = data.files || {} const keys = Object.keys(data) if (keys.length === 0) { - self.client.emit('warning', new Error('invalid scrape response')) + this.client.emit('warning', new Error('invalid scrape response')) return } @@ -318,51 +307,47 @@ class WebSocketTracker extends Tracker { // TODO: optionally handle data.flags.min_request_interval // (separate from announce interval) const response = Object.assign(data[infoHash], { - announce: self.announceUrl, + announce: this.announceUrl, infoHash: common.binaryToHex(infoHash) }) - self.client.emit('scrape', response) + this.client.emit('scrape', response) }) } _onSocketClose () { - const self = this - if (self.destroyed) return - self.destroy() - self._startReconnectTimer() + if (this.destroyed) return + this.destroy() + this._startReconnectTimer() } _onSocketError (err) { - const self = this - if (self.destroyed) return - self.destroy() + if (this.destroyed) return + this.destroy() // errors will often happen if a tracker is offline, so don't treat it as fatal - self.client.emit('warning', err) - self._startReconnectTimer() + this.client.emit('warning', err) + this._startReconnectTimer() } _startReconnectTimer () { - const self = this - const ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, self.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM) + const ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, this.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM) - self.reconnecting = true - clearTimeout(self.reconnectTimer) - self.reconnectTimer = setTimeout(() => { - self.retries++ - self._openSocket() + this.reconnecting = true + clearTimeout(this.reconnectTimer) + this.reconnectTimer = setTimeout(() => { + this.retries++ + this._openSocket() }, ms) - if (self.reconnectTimer.unref) self.reconnectTimer.unref() + if (this.reconnectTimer.unref) this.reconnectTimer.unref() debug('reconnecting socket in %s ms', ms) } _send (params) { - const self = this - if (self.destroyed) return - self.expectingResponse = true + if (this.destroyed) return + this.expectingResponse = true const message = JSON.stringify(params) debug('send %s', message) - self.socket.send(message) + this.socket.send(message) } _generateOffers (numwant, cb) {