replace self with this

This commit is contained in:
Jimmy Wärting 2018-10-03 15:06:38 +02:00
parent 386e0a5fbe
commit c8ceaee306
4 changed files with 182 additions and 208 deletions

View File

@ -20,47 +20,44 @@ class HTTPTracker extends Tracker {
constructor (client, announceUrl, opts) { constructor (client, announceUrl, opts) {
super(client, announceUrl) super(client, announceUrl)
const self = this
debug('new http tracker %s', announceUrl) debug('new http tracker %s', announceUrl)
// Determine scrape url (if http tracker supports it) // 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) { if (match) {
const pre = self.announceUrl.slice(0, match.index) const pre = this.announceUrl.slice(0, match.index)
const post = self.announceUrl.slice(match.index + 9) const post = this.announceUrl.slice(match.index + 9)
self.scrapeUrl = `${pre}/scrape${post}` this.scrapeUrl = `${pre}/scrape${post}`
} }
self.cleanupFns = [] this.cleanupFns = []
self.maybeDestroyCleanup = null this.maybeDestroyCleanup = null
} }
announce (opts) { announce (opts) {
const self = this if (this.destroyed) return
if (self.destroyed) return
const params = Object.assign({}, opts, { const params = Object.assign({}, opts, {
compact: (opts.compact == null) ? 1 : opts.compact, compact: (opts.compact == null) ? 1 : opts.compact,
info_hash: self.client._infoHashBinary, info_hash: this.client._infoHashBinary,
peer_id: self.client._peerIdBinary, peer_id: this.client._peerIdBinary,
port: self.client._port 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) => { this._request(this.announceUrl, params, (err, data) => {
if (err) return self.client.emit('warning', err) if (err) return this.client.emit('warning', err)
self._onAnnounceResponse(data) this._onAnnounceResponse(data)
}) })
} }
scrape (opts) { scrape (opts) {
const self = this if (this.destroyed) return
if (self.destroyed) return
if (!self.scrapeUrl) { if (!this.scrapeUrl) {
self.client.emit('error', new Error(`scrape not supported ${self.announceUrl}`)) this.client.emit('error', new Error(`scrape not supported ${this.announceUrl}`))
return return
} }
@ -68,24 +65,24 @@ class HTTPTracker extends Tracker {
? opts.infoHash.map(infoHash => { ? opts.infoHash.map(infoHash => {
return infoHash.toString('binary') return infoHash.toString('binary')
}) })
: (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary
const params = { const params = {
info_hash: infoHashes info_hash: infoHashes
} }
self._request(self.scrapeUrl, params, (err, data) => { this._request(this.scrapeUrl, params, (err, data) => {
if (err) return self.client.emit('warning', err) if (err) return this.client.emit('warning', err)
self._onScrapeResponse(data) this._onScrapeResponse(data)
}) })
} }
destroy (cb) { destroy (cb) {
const self = this const self = this
if (self.destroyed) return cb(null) if (this.destroyed) return cb(null)
self.destroyed = true this.destroyed = true
clearInterval(self.interval) clearInterval(this.interval)
// If there are no pending requests, destroy immediately. // 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 // Otherwise, wait a short time for pending requests to complete, then force
// destroy them. // destroy them.
@ -93,8 +90,8 @@ class HTTPTracker extends Tracker {
// But, if all pending requests complete before the timeout fires, do cleanup // But, if all pending requests complete before the timeout fires, do cleanup
// right away. // right away.
self.maybeDestroyCleanup = () => { this.maybeDestroyCleanup = () => {
if (self.cleanupFns.length === 0) destroyCleanup() if (this.cleanupFns.length === 0) destroyCleanup()
} }
function destroyCleanup () { function destroyCleanup () {
@ -116,13 +113,13 @@ class HTTPTracker extends Tracker {
const u = requestUrl + (!requestUrl.includes('?') ? '?' : '&') + const u = requestUrl + (!requestUrl.includes('?') ? '?' : '&') +
common.querystringStringify(params) common.querystringStringify(params)
self.cleanupFns.push(cleanup) this.cleanupFns.push(cleanup)
let request = get.concat({ let request = get.concat({
url: u, url: u,
timeout: common.REQUEST_TIMEOUT, timeout: common.REQUEST_TIMEOUT,
headers: { headers: {
'user-agent': self.client._userAgent || '' 'user-agent': this.client._userAgent || ''
} }
}, onResponse) }, onResponse)
@ -171,22 +168,20 @@ class HTTPTracker extends Tracker {
} }
_onAnnounceResponse (data) { _onAnnounceResponse (data) {
const self = this
const interval = data.interval || data['min interval'] const interval = data.interval || data['min interval']
if (interval) self.setInterval(interval * 1000) if (interval) this.setInterval(interval * 1000)
const trackerId = data['tracker id'] const trackerId = data['tracker id']
if (trackerId) { if (trackerId) {
// If absent, do not discard previous trackerId value // If absent, do not discard previous trackerId value
self._trackerId = trackerId this._trackerId = trackerId
} }
const response = Object.assign({}, data, { const response = Object.assign({}, data, {
announce: self.announceUrl, announce: this.announceUrl,
infoHash: common.binaryToHex(data.info_hash) infoHash: common.binaryToHex(data.info_hash)
}) })
self.client.emit('update', response) this.client.emit('update', response)
let addrs let addrs
if (Buffer.isBuffer(data.peers)) { if (Buffer.isBuffer(data.peers)) {
@ -194,15 +189,15 @@ class HTTPTracker extends Tracker {
try { try {
addrs = compact2string.multi(data.peers) addrs = compact2string.multi(data.peers)
} catch (err) { } catch (err) {
return self.client.emit('warning', err) return this.client.emit('warning', err)
} }
addrs.forEach(addr => { addrs.forEach(addr => {
self.client.emit('peer', addr) this.client.emit('peer', addr)
}) })
} else if (Array.isArray(data.peers)) { } else if (Array.isArray(data.peers)) {
// tracker returned normal response // tracker returned normal response
data.peers.forEach(peer => { 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 { try {
addrs = compact2string.multi6(data.peers6) addrs = compact2string.multi6(data.peers6)
} catch (err) { } catch (err) {
return self.client.emit('warning', err) return this.client.emit('warning', err)
} }
addrs.forEach(addr => { addrs.forEach(addr => {
self.client.emit('peer', addr) this.client.emit('peer', addr)
}) })
} else if (Array.isArray(data.peers6)) { } else if (Array.isArray(data.peers6)) {
// tracker returned normal response // tracker returned normal response
@ -222,20 +217,19 @@ class HTTPTracker extends Tracker {
const ip = /^\[/.test(peer.ip) || !/:/.test(peer.ip) const ip = /^\[/.test(peer.ip) || !/:/.test(peer.ip)
? peer.ip /* ipv6 w/ brackets or domain name */ ? peer.ip /* ipv6 w/ brackets or domain name */
: `[${peer.ip}]` /* ipv6 without brackets */ : `[${peer.ip}]` /* ipv6 without brackets */
self.client.emit('peer', `${ip}:${peer.port}`) this.client.emit('peer', `${ip}:${peer.port}`)
}) })
} }
} }
_onScrapeResponse (data) { _onScrapeResponse (data) {
const self = this
// NOTE: the unofficial spec says to use the 'files' key, 'host' has been // NOTE: the unofficial spec says to use the 'files' key, 'host' has been
// seen in practice // seen in practice
data = data.files || data.host || {} data = data.files || data.host || {}
const keys = Object.keys(data) const keys = Object.keys(data)
if (keys.length === 0) { if (keys.length === 0) {
self.client.emit('warning', new Error('invalid scrape response')) this.client.emit('warning', new Error('invalid scrape response'))
return return
} }
@ -243,10 +237,10 @@ class HTTPTracker extends Tracker {
// TODO: optionally handle data.flags.min_request_interval // TODO: optionally handle data.flags.min_request_interval
// (separate from announce interval) // (separate from announce interval)
const response = Object.assign(data[infoHash], { const response = Object.assign(data[infoHash], {
announce: self.announceUrl, announce: this.announceUrl,
infoHash: common.binaryToHex(infoHash) infoHash: common.binaryToHex(infoHash)
}) })
self.client.emit('scrape', response) this.client.emit('scrape', response)
}) })
} }
} }

View File

@ -4,25 +4,23 @@ class Tracker extends EventEmitter {
constructor (client, announceUrl) { constructor (client, announceUrl) {
super() super()
const self = this this.client = client
self.client = client this.announceUrl = announceUrl
self.announceUrl = announceUrl
self.interval = null this.interval = null
self.destroyed = false this.destroyed = false
} }
setInterval (intervalMs) { setInterval (intervalMs) {
const self = this if (intervalMs == null) intervalMs = this.DEFAULT_ANNOUNCE_INTERVAL
if (intervalMs == null) intervalMs = self.DEFAULT_ANNOUNCE_INTERVAL
clearInterval(self.interval) clearInterval(this.interval)
if (intervalMs) { if (intervalMs) {
self.interval = setInterval(() => { this.interval = setInterval(() => {
self.announce(self.client._defaultAnnounceOpts()) this.announce(this.client._defaultAnnounceOpts())
}, intervalMs) }, intervalMs)
if (self.interval.unref) self.interval.unref() if (this.interval.unref) this.interval.unref()
} }
} }
} }

View File

@ -20,34 +20,31 @@ const Tracker = require('./tracker')
class UDPTracker extends Tracker { class UDPTracker extends Tracker {
constructor (client, announceUrl, opts) { constructor (client, announceUrl, opts) {
super(client, announceUrl) super(client, announceUrl)
const self = this
debug('new udp tracker %s', announceUrl) debug('new udp tracker %s', announceUrl)
self.cleanupFns = [] this.cleanupFns = []
self.maybeDestroyCleanup = null this.maybeDestroyCleanup = null
} }
announce (opts) { announce (opts) {
const self = this if (this.destroyed) return
if (self.destroyed) return this._request(opts)
self._request(opts)
} }
scrape (opts) { scrape (opts) {
const self = this if (this.destroyed) return
if (self.destroyed) return
opts._scrape = true opts._scrape = true
self._request(opts) // udp scrape uses same announce url this._request(opts) // udp scrape uses same announce url
} }
destroy (cb) { destroy (cb) {
const self = this const self = this
if (self.destroyed) return cb(null) if (this.destroyed) return cb(null)
self.destroyed = true this.destroyed = true
clearInterval(self.interval) clearInterval(this.interval)
// If there are no pending requests, destroy immediately. // 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 // Otherwise, wait a short time for pending requests to complete, then force
// destroy them. // destroy them.
@ -55,8 +52,8 @@ class UDPTracker extends Tracker {
// But, if all pending requests complete before the timeout fires, do cleanup // But, if all pending requests complete before the timeout fires, do cleanup
// right away. // right away.
self.maybeDestroyCleanup = () => { this.maybeDestroyCleanup = () => {
if (self.cleanupFns.length === 0) destroyCleanup() if (this.cleanupFns.length === 0) destroyCleanup()
} }
function destroyCleanup () { function destroyCleanup () {
@ -76,7 +73,7 @@ class UDPTracker extends Tracker {
_request (opts) { _request (opts) {
const self = this const self = this
if (!opts) opts = {} if (!opts) opts = {}
const parsedUrl = url.parse(self.announceUrl) const parsedUrl = url.parse(this.announceUrl)
let transactionId = genTransactionId() let transactionId = genTransactionId()
let socket = dgram.createSocket('udp4') let socket = dgram.createSocket('udp4')
@ -88,7 +85,7 @@ class UDPTracker extends Tracker {
}, common.REQUEST_TIMEOUT) }, common.REQUEST_TIMEOUT)
if (timeout.unref) timeout.unref() if (timeout.unref) timeout.unref()
self.cleanupFns.push(cleanup) this.cleanupFns.push(cleanup)
send(Buffer.concat([ send(Buffer.concat([
common.CONNECTION_ID, common.CONNECTION_ID,

View File

@ -19,61 +19,58 @@ const OFFER_TIMEOUT = 50 * 1000
class WebSocketTracker extends Tracker { class WebSocketTracker extends Tracker {
constructor (client, announceUrl, opts) { constructor (client, announceUrl, opts) {
super(client, announceUrl) super(client, announceUrl)
const self = this
debug('new websocket tracker %s', announceUrl) debug('new websocket tracker %s', announceUrl)
self.peers = {} // peers (offer id -> peer) this.peers = {} // peers (offer id -> peer)
self.socket = null this.socket = null
self.reconnecting = false this.reconnecting = false
self.retries = 0 this.retries = 0
self.reconnectTimer = null this.reconnectTimer = null
// Simple boolean flag to track whether the socket has received data from // Simple boolean flag to track whether the socket has received data from
// the websocket server since the last time socket.send() was called. // the websocket server since the last time socket.send() was called.
self.expectingResponse = false this.expectingResponse = false
self._openSocket() this._openSocket()
} }
announce (opts) { announce (opts) {
const self = this if (this.destroyed || this.reconnecting) return
if (self.destroyed || self.reconnecting) return if (!this.socket.connected) {
if (!self.socket.connected) { this.socket.once('connect', () => {
self.socket.once('connect', () => { this.announce(opts)
self.announce(opts)
}) })
return return
} }
const params = Object.assign({}, opts, { const params = Object.assign({}, opts, {
action: 'announce', action: 'announce',
info_hash: self.client._infoHashBinary, info_hash: this.client._infoHashBinary,
peer_id: self.client._peerIdBinary 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') { if (opts.event === 'stopped' || opts.event === 'completed') {
// Don't include offers with 'stopped' or 'completed' event // Don't include offers with 'stopped' or 'completed' event
self._send(params) this._send(params)
} else { } else {
// Limit the number of offers that are generated, since it can be slow // Limit the number of offers that are generated, since it can be slow
const numwant = Math.min(opts.numwant, 10) const numwant = Math.min(opts.numwant, 10)
self._generateOffers(numwant, offers => { this._generateOffers(numwant, offers => {
params.numwant = numwant params.numwant = numwant
params.offers = offers params.offers = offers
self._send(params) this._send(params)
}) })
} }
} }
scrape (opts) { scrape (opts) {
const self = this if (this.destroyed || this.reconnecting) return
if (self.destroyed || self.reconnecting) return if (!this.socket.connected) {
if (!self.socket.connected) { this.socket.once('connect', () => {
self.socket.once('connect', () => { this.scrape(opts)
self.scrape(opts)
}) })
return return
} }
@ -82,60 +79,58 @@ class WebSocketTracker extends Tracker {
? opts.infoHash.map(infoHash => { ? opts.infoHash.map(infoHash => {
return infoHash.toString('binary') return infoHash.toString('binary')
}) })
: (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary
const params = { const params = {
action: 'scrape', action: 'scrape',
info_hash: infoHashes info_hash: infoHashes
} }
self._send(params) this._send(params)
} }
destroy (cb) { destroy (cb = noop) {
const self = this if (this.destroyed) return cb(null)
if (!cb) cb = noop
if (self.destroyed) return cb(null)
self.destroyed = true this.destroyed = true
clearInterval(self.interval) clearInterval(this.interval)
clearTimeout(self.reconnectTimer) clearTimeout(this.reconnectTimer)
// Destroy peers // Destroy peers
for (const peerId in self.peers) { for (const peerId in this.peers) {
const peer = self.peers[peerId] const peer = this.peers[peerId]
clearTimeout(peer.trackerTimeout) clearTimeout(peer.trackerTimeout)
peer.destroy() peer.destroy()
} }
self.peers = null this.peers = null
if (self.socket) { if (this.socket) {
self.socket.removeListener('connect', self._onSocketConnectBound) this.socket.removeListener('connect', this._onSocketConnectBound)
self.socket.removeListener('data', self._onSocketDataBound) this.socket.removeListener('data', this._onSocketDataBound)
self.socket.removeListener('close', self._onSocketCloseBound) this.socket.removeListener('close', this._onSocketCloseBound)
self.socket.removeListener('error', self._onSocketErrorBound) this.socket.removeListener('error', this._onSocketErrorBound)
self.socket = null this.socket = null
} }
self._onSocketConnectBound = null this._onSocketConnectBound = null
self._onSocketErrorBound = null this._onSocketErrorBound = null
self._onSocketDataBound = null this._onSocketDataBound = null
self._onSocketCloseBound = null this._onSocketCloseBound = null
if (socketPool[self.announceUrl]) { if (socketPool[this.announceUrl]) {
socketPool[self.announceUrl].consumers -= 1 socketPool[this.announceUrl].consumers -= 1
} }
// Other instances are using the socket, so there's nothing left to do here // 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] let socket = socketPool[this.announceUrl]
delete socketPool[self.announceUrl] delete socketPool[this.announceUrl]
socket.on('error', noop) // ignore all future errors socket.on('error', noop) // ignore all future errors
socket.once('close', cb) socket.once('close', cb)
// If there is no data response expected, destroy immediately. // 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 // Otherwise, wait a short time for potential responses to come in from the
// server, then force close the socket. // server, then force close the socket.
@ -157,147 +152,142 @@ class WebSocketTracker extends Tracker {
} }
_openSocket () { _openSocket () {
const self = this this.destroyed = false
self.destroyed = false
if (!self.peers) self.peers = {} if (!this.peers) this.peers = {}
self._onSocketConnectBound = () => { this._onSocketConnectBound = () => {
self._onSocketConnect() this._onSocketConnect()
} }
self._onSocketErrorBound = err => { this._onSocketErrorBound = err => {
self._onSocketError(err) this._onSocketError(err)
} }
self._onSocketDataBound = data => { this._onSocketDataBound = data => {
self._onSocketData(data) this._onSocketData(data)
} }
self._onSocketCloseBound = () => { this._onSocketCloseBound = () => {
self._onSocketClose() this._onSocketClose()
} }
self.socket = socketPool[self.announceUrl] this.socket = socketPool[this.announceUrl]
if (self.socket) { if (this.socket) {
socketPool[self.announceUrl].consumers += 1 socketPool[this.announceUrl].consumers += 1
} else { } else {
self.socket = socketPool[self.announceUrl] = new Socket(self.announceUrl) this.socket = socketPool[this.announceUrl] = new Socket(this.announceUrl)
self.socket.consumers = 1 this.socket.consumers = 1
self.socket.once('connect', self._onSocketConnectBound) this.socket.once('connect', this._onSocketConnectBound)
} }
self.socket.on('data', self._onSocketDataBound) this.socket.on('data', this._onSocketDataBound)
self.socket.once('close', self._onSocketCloseBound) this.socket.once('close', this._onSocketCloseBound)
self.socket.once('error', self._onSocketErrorBound) this.socket.once('error', this._onSocketErrorBound)
} }
_onSocketConnect () { _onSocketConnect () {
const self = this if (this.destroyed) return
if (self.destroyed) return
if (self.reconnecting) { if (this.reconnecting) {
self.reconnecting = false this.reconnecting = false
self.retries = 0 this.retries = 0
self.announce(self.client._defaultAnnounceOpts()) this.announce(this.client._defaultAnnounceOpts())
} }
} }
_onSocketData (data) { _onSocketData (data) {
const self = this if (this.destroyed) return
if (self.destroyed) return
self.expectingResponse = false this.expectingResponse = false
try { try {
data = JSON.parse(data) data = JSON.parse(data)
} catch (err) { } catch (err) {
self.client.emit('warning', new Error('Invalid tracker response')) this.client.emit('warning', new Error('Invalid tracker response'))
return return
} }
if (data.action === 'announce') { if (data.action === 'announce') {
self._onAnnounceResponse(data) this._onAnnounceResponse(data)
} else if (data.action === 'scrape') { } else if (data.action === 'scrape') {
self._onScrapeResponse(data) this._onScrapeResponse(data)
} else { } 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) { _onAnnounceResponse (data) {
const self = this if (data.info_hash !== this.client._infoHashBinary) {
if (data.info_hash !== self.client._infoHashBinary) {
debug( debug(
'ignoring websocket data from %s for %s (looking for %s: reused socket)', '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 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 // ignore offers/answers from this client
return return
} }
debug( debug(
'received %s from %s for %s', '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'] 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'] 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'] const interval = data.interval || data['min interval']
if (interval) self.setInterval(interval * 1000) if (interval) this.setInterval(interval * 1000)
const trackerId = data['tracker id'] const trackerId = data['tracker id']
if (trackerId) { if (trackerId) {
// If absent, do not discard previous trackerId value // If absent, do not discard previous trackerId value
self._trackerId = trackerId this._trackerId = trackerId
} }
if (data.complete != null) { if (data.complete != null) {
const response = Object.assign({}, data, { const response = Object.assign({}, data, {
announce: self.announceUrl, announce: this.announceUrl,
infoHash: common.binaryToHex(data.info_hash) infoHash: common.binaryToHex(data.info_hash)
}) })
self.client.emit('update', response) this.client.emit('update', response)
} }
let peer let peer
if (data.offer && data.peer_id) { if (data.offer && data.peer_id) {
debug('creating peer (from remote offer)') debug('creating peer (from remote offer)')
peer = self._createPeer() peer = this._createPeer()
peer.id = common.binaryToHex(data.peer_id) peer.id = common.binaryToHex(data.peer_id)
peer.once('signal', answer => { peer.once('signal', answer => {
const params = { const params = {
action: 'announce', action: 'announce',
info_hash: self.client._infoHashBinary, info_hash: this.client._infoHashBinary,
peer_id: self.client._peerIdBinary, peer_id: this.client._peerIdBinary,
to_peer_id: data.peer_id, to_peer_id: data.peer_id,
answer, answer,
offer_id: data.offer_id offer_id: data.offer_id
} }
if (self._trackerId) params.trackerid = self._trackerId if (this._trackerId) params.trackerid = this._trackerId
self._send(params) this._send(params)
}) })
peer.signal(data.offer) peer.signal(data.offer)
self.client.emit('peer', peer) this.client.emit('peer', peer)
} }
if (data.answer && data.peer_id) { if (data.answer && data.peer_id) {
const offerId = common.binaryToHex(data.offer_id) const offerId = common.binaryToHex(data.offer_id)
peer = self.peers[offerId] peer = this.peers[offerId]
if (peer) { if (peer) {
peer.id = common.binaryToHex(data.peer_id) peer.id = common.binaryToHex(data.peer_id)
peer.signal(data.answer) peer.signal(data.answer)
self.client.emit('peer', peer) this.client.emit('peer', peer)
clearTimeout(peer.trackerTimeout) clearTimeout(peer.trackerTimeout)
peer.trackerTimeout = null peer.trackerTimeout = null
delete self.peers[offerId] delete this.peers[offerId]
} else { } else {
debug(`got unexpected answer: ${JSON.stringify(data.answer)}`) debug(`got unexpected answer: ${JSON.stringify(data.answer)}`)
} }
@ -305,12 +295,11 @@ class WebSocketTracker extends Tracker {
} }
_onScrapeResponse (data) { _onScrapeResponse (data) {
const self = this
data = data.files || {} data = data.files || {}
const keys = Object.keys(data) const keys = Object.keys(data)
if (keys.length === 0) { if (keys.length === 0) {
self.client.emit('warning', new Error('invalid scrape response')) this.client.emit('warning', new Error('invalid scrape response'))
return return
} }
@ -318,51 +307,47 @@ class WebSocketTracker extends Tracker {
// TODO: optionally handle data.flags.min_request_interval // TODO: optionally handle data.flags.min_request_interval
// (separate from announce interval) // (separate from announce interval)
const response = Object.assign(data[infoHash], { const response = Object.assign(data[infoHash], {
announce: self.announceUrl, announce: this.announceUrl,
infoHash: common.binaryToHex(infoHash) infoHash: common.binaryToHex(infoHash)
}) })
self.client.emit('scrape', response) this.client.emit('scrape', response)
}) })
} }
_onSocketClose () { _onSocketClose () {
const self = this if (this.destroyed) return
if (self.destroyed) return this.destroy()
self.destroy() this._startReconnectTimer()
self._startReconnectTimer()
} }
_onSocketError (err) { _onSocketError (err) {
const self = this if (this.destroyed) return
if (self.destroyed) return this.destroy()
self.destroy()
// errors will often happen if a tracker is offline, so don't treat it as fatal // errors will often happen if a tracker is offline, so don't treat it as fatal
self.client.emit('warning', err) this.client.emit('warning', err)
self._startReconnectTimer() this._startReconnectTimer()
} }
_startReconnectTimer () { _startReconnectTimer () {
const self = this const ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, this.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM)
const ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, self.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM)
self.reconnecting = true this.reconnecting = true
clearTimeout(self.reconnectTimer) clearTimeout(this.reconnectTimer)
self.reconnectTimer = setTimeout(() => { this.reconnectTimer = setTimeout(() => {
self.retries++ this.retries++
self._openSocket() this._openSocket()
}, ms) }, ms)
if (self.reconnectTimer.unref) self.reconnectTimer.unref() if (this.reconnectTimer.unref) this.reconnectTimer.unref()
debug('reconnecting socket in %s ms', ms) debug('reconnecting socket in %s ms', ms)
} }
_send (params) { _send (params) {
const self = this if (this.destroyed) return
if (self.destroyed) return this.expectingResponse = true
self.expectingResponse = true
const message = JSON.stringify(params) const message = JSON.stringify(params)
debug('send %s', message) debug('send %s', message)
self.socket.send(message) this.socket.send(message)
} }
_generateOffers (numwant, cb) { _generateOffers (numwant, cb) {