mirror of
https://github.com/webtorrent/bittorrent-tracker.git
synced 2025-01-31 02:21:36 +00:00
Merge branch 'master' into modernize_lib_client
This commit is contained in:
commit
d410c6c088
268
client.js
268
client.js
@ -1,21 +1,16 @@
|
|||||||
module.exports = Client
|
const { Buffer } = require('safe-buffer')
|
||||||
|
const debug = require('debug')('bittorrent-tracker:client')
|
||||||
|
const EventEmitter = require('events')
|
||||||
|
const once = require('once')
|
||||||
|
const parallel = require('run-parallel')
|
||||||
|
const Peer = require('simple-peer')
|
||||||
|
const uniq = require('uniq')
|
||||||
|
const url = require('url')
|
||||||
|
|
||||||
var Buffer = require('safe-buffer').Buffer
|
const common = require('./lib/common')
|
||||||
var debug = require('debug')('bittorrent-tracker:client')
|
const HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
|
||||||
var EventEmitter = require('events').EventEmitter
|
const UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
|
||||||
var inherits = require('inherits')
|
const WebSocketTracker = require('./lib/client/websocket-tracker')
|
||||||
var once = require('once')
|
|
||||||
var parallel = require('run-parallel')
|
|
||||||
var Peer = require('simple-peer')
|
|
||||||
var uniq = require('uniq')
|
|
||||||
var url = require('url')
|
|
||||||
|
|
||||||
var common = require('./lib/common')
|
|
||||||
var HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
|
|
||||||
var UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
|
|
||||||
var WebSocketTracker = require('./lib/client/websocket-tracker')
|
|
||||||
|
|
||||||
inherits(Client, EventEmitter)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BitTorrent tracker client.
|
* BitTorrent tracker client.
|
||||||
@ -32,48 +27,46 @@ inherits(Client, EventEmitter)
|
|||||||
* @param {number} opts.userAgent User-Agent header for http requests
|
* @param {number} opts.userAgent User-Agent header for http requests
|
||||||
* @param {number} opts.wrtc custom webrtc impl (useful in node.js)
|
* @param {number} opts.wrtc custom webrtc impl (useful in node.js)
|
||||||
*/
|
*/
|
||||||
function Client (opts) {
|
class Client extends EventEmitter {
|
||||||
var self = this
|
constructor (opts = {}) {
|
||||||
if (!(self instanceof Client)) return new Client(opts)
|
super()
|
||||||
EventEmitter.call(self)
|
|
||||||
if (!opts) opts = {}
|
|
||||||
|
|
||||||
if (!opts.peerId) throw new Error('Option `peerId` is required')
|
if (!opts.peerId) throw new Error('Option `peerId` is required')
|
||||||
if (!opts.infoHash) throw new Error('Option `infoHash` is required')
|
if (!opts.infoHash) throw new Error('Option `infoHash` is required')
|
||||||
if (!opts.announce) throw new Error('Option `announce` is required')
|
if (!opts.announce) throw new Error('Option `announce` is required')
|
||||||
if (!process.browser && !opts.port) throw new Error('Option `port` is required')
|
if (!process.browser && !opts.port) throw new Error('Option `port` is required')
|
||||||
|
|
||||||
self.peerId = typeof opts.peerId === 'string'
|
this.peerId = typeof opts.peerId === 'string'
|
||||||
? opts.peerId
|
? opts.peerId
|
||||||
: opts.peerId.toString('hex')
|
: opts.peerId.toString('hex')
|
||||||
self._peerIdBuffer = Buffer.from(self.peerId, 'hex')
|
this._peerIdBuffer = Buffer.from(this.peerId, 'hex')
|
||||||
self._peerIdBinary = self._peerIdBuffer.toString('binary')
|
this._peerIdBinary = this._peerIdBuffer.toString('binary')
|
||||||
|
|
||||||
self.infoHash = typeof opts.infoHash === 'string'
|
this.infoHash = typeof opts.infoHash === 'string'
|
||||||
? opts.infoHash.toLowerCase()
|
? opts.infoHash.toLowerCase()
|
||||||
: opts.infoHash.toString('hex')
|
: opts.infoHash.toString('hex')
|
||||||
self._infoHashBuffer = Buffer.from(self.infoHash, 'hex')
|
this._infoHashBuffer = Buffer.from(this.infoHash, 'hex')
|
||||||
self._infoHashBinary = self._infoHashBuffer.toString('binary')
|
this._infoHashBinary = this._infoHashBuffer.toString('binary')
|
||||||
|
|
||||||
debug('new client %s', self.infoHash)
|
debug('new client %s', this.infoHash)
|
||||||
|
|
||||||
self.destroyed = false
|
this.destroyed = false
|
||||||
|
|
||||||
self._port = opts.port
|
this._port = opts.port
|
||||||
self._getAnnounceOpts = opts.getAnnounceOpts
|
this._getAnnounceOpts = opts.getAnnounceOpts
|
||||||
self._rtcConfig = opts.rtcConfig
|
this._rtcConfig = opts.rtcConfig
|
||||||
self._userAgent = opts.userAgent
|
this._userAgent = opts.userAgent
|
||||||
|
|
||||||
// Support lazy 'wrtc' module initialization
|
// Support lazy 'wrtc' module initialization
|
||||||
// See: https://github.com/webtorrent/webtorrent-hybrid/issues/46
|
// See: https://github.com/webtorrent/webtorrent-hybrid/issues/46
|
||||||
self._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc
|
this._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc
|
||||||
|
|
||||||
var announce = typeof opts.announce === 'string'
|
let announce = typeof opts.announce === 'string'
|
||||||
? [ opts.announce ]
|
? [ opts.announce ]
|
||||||
: opts.announce == null ? [] : opts.announce
|
: opts.announce == null ? [] : opts.announce
|
||||||
|
|
||||||
// Remove trailing slash from trackers to catch duplicates
|
// Remove trailing slash from trackers to catch duplicates
|
||||||
announce = announce.map(function (announceUrl) {
|
announce = announce.map(announceUrl => {
|
||||||
announceUrl = announceUrl.toString()
|
announceUrl = announceUrl.toString()
|
||||||
if (announceUrl[announceUrl.length - 1] === '/') {
|
if (announceUrl[announceUrl.length - 1] === '/') {
|
||||||
announceUrl = announceUrl.substring(0, announceUrl.length - 1)
|
announceUrl = announceUrl.substring(0, announceUrl.length - 1)
|
||||||
@ -82,86 +75,36 @@ function Client (opts) {
|
|||||||
})
|
})
|
||||||
announce = uniq(announce)
|
announce = uniq(announce)
|
||||||
|
|
||||||
var webrtcSupport = self._wrtc !== false && (!!self._wrtc || Peer.WEBRTC_SUPPORT)
|
const webrtcSupport = this._wrtc !== false && (!!this._wrtc || Peer.WEBRTC_SUPPORT)
|
||||||
|
|
||||||
self._trackers = announce
|
const nextTickWarn = err => {
|
||||||
.map(function (announceUrl) {
|
process.nextTick(() => {
|
||||||
var protocol = url.parse(announceUrl).protocol
|
this.emit('warning', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this._trackers = announce
|
||||||
|
.map(announceUrl => {
|
||||||
|
const protocol = url.parse(announceUrl).protocol
|
||||||
if ((protocol === 'http:' || protocol === 'https:') &&
|
if ((protocol === 'http:' || protocol === 'https:') &&
|
||||||
typeof HTTPTracker === 'function') {
|
typeof HTTPTracker === 'function') {
|
||||||
return new HTTPTracker(self, announceUrl)
|
return new HTTPTracker(this, announceUrl)
|
||||||
} else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
|
} else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
|
||||||
return new UDPTracker(self, announceUrl)
|
return new UDPTracker(this, announceUrl)
|
||||||
} else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
|
} else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
|
||||||
// Skip ws:// trackers on https:// sites because they throw SecurityError
|
// Skip ws:// trackers on https:// sites because they throw SecurityError
|
||||||
if (protocol === 'ws:' && typeof window !== 'undefined' &&
|
if (protocol === 'ws:' && typeof window !== 'undefined' &&
|
||||||
window.location.protocol === 'https:') {
|
window.location.protocol === 'https:') {
|
||||||
nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl))
|
nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return new WebSocketTracker(self, announceUrl)
|
return new WebSocketTracker(this, announceUrl)
|
||||||
} else {
|
} else {
|
||||||
nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl))
|
nextTickWarn(new Error(`Unsupported tracker protocol: ${announceUrl}`))
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
function nextTickWarn (err) {
|
|
||||||
process.nextTick(function () {
|
|
||||||
self.emit('warning', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple convenience function to scrape a tracker for an info hash without needing to
|
|
||||||
* create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
|
|
||||||
* torrents at the same time.
|
|
||||||
* @params {Object} opts
|
|
||||||
* @param {string|Array.<string>} opts.infoHash
|
|
||||||
* @param {string} opts.announce
|
|
||||||
* @param {function} cb
|
|
||||||
*/
|
|
||||||
Client.scrape = function (opts, cb) {
|
|
||||||
cb = once(cb)
|
|
||||||
|
|
||||||
if (!opts.infoHash) throw new Error('Option `infoHash` is required')
|
|
||||||
if (!opts.announce) throw new Error('Option `announce` is required')
|
|
||||||
|
|
||||||
var clientOpts = Object.assign({}, opts, {
|
|
||||||
infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash,
|
|
||||||
peerId: Buffer.from('01234567890123456789'), // dummy value
|
|
||||||
port: 6881 // dummy value
|
|
||||||
})
|
|
||||||
|
|
||||||
var client = new Client(clientOpts)
|
|
||||||
client.once('error', cb)
|
|
||||||
client.once('warning', cb)
|
|
||||||
|
|
||||||
var len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1
|
|
||||||
var results = {}
|
|
||||||
client.on('scrape', function (data) {
|
|
||||||
len -= 1
|
|
||||||
results[data.infoHash] = data
|
|
||||||
if (len === 0) {
|
|
||||||
client.destroy()
|
|
||||||
var keys = Object.keys(results)
|
|
||||||
if (keys.length === 1) {
|
|
||||||
cb(null, results[keys[0]])
|
|
||||||
} else {
|
|
||||||
cb(null, results)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
opts.infoHash = Array.isArray(opts.infoHash)
|
|
||||||
? opts.infoHash.map(function (infoHash) {
|
|
||||||
return Buffer.from(infoHash, 'hex')
|
|
||||||
})
|
|
||||||
: Buffer.from(opts.infoHash, 'hex')
|
|
||||||
client.scrape({ infoHash: opts.infoHash })
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,15 +114,14 @@ Client.scrape = function (opts, cb) {
|
|||||||
* @param {number=} opts.downloaded
|
* @param {number=} opts.downloaded
|
||||||
* @param {number=} opts.left (if not set, calculated automatically)
|
* @param {number=} opts.left (if not set, calculated automatically)
|
||||||
*/
|
*/
|
||||||
Client.prototype.start = function (opts) {
|
start (opts) {
|
||||||
var self = this
|
|
||||||
debug('send `start`')
|
debug('send `start`')
|
||||||
opts = self._defaultAnnounceOpts(opts)
|
opts = this._defaultAnnounceOpts(opts)
|
||||||
opts.event = 'started'
|
opts.event = 'started'
|
||||||
self._announce(opts)
|
this._announce(opts)
|
||||||
|
|
||||||
// start announcing on intervals
|
// start announcing on intervals
|
||||||
self._trackers.forEach(function (tracker) {
|
this._trackers.forEach(tracker => {
|
||||||
tracker.setInterval()
|
tracker.setInterval()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -192,12 +134,11 @@ Client.prototype.start = function (opts) {
|
|||||||
* @param {number=} opts.numwant
|
* @param {number=} opts.numwant
|
||||||
* @param {number=} opts.left (if not set, calculated automatically)
|
* @param {number=} opts.left (if not set, calculated automatically)
|
||||||
*/
|
*/
|
||||||
Client.prototype.stop = function (opts) {
|
stop (opts) {
|
||||||
var self = this
|
|
||||||
debug('send `stop`')
|
debug('send `stop`')
|
||||||
opts = self._defaultAnnounceOpts(opts)
|
opts = this._defaultAnnounceOpts(opts)
|
||||||
opts.event = 'stopped'
|
opts.event = 'stopped'
|
||||||
self._announce(opts)
|
this._announce(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,13 +149,12 @@ Client.prototype.stop = function (opts) {
|
|||||||
* @param {number=} opts.numwant
|
* @param {number=} opts.numwant
|
||||||
* @param {number=} opts.left (if not set, calculated automatically)
|
* @param {number=} opts.left (if not set, calculated automatically)
|
||||||
*/
|
*/
|
||||||
Client.prototype.complete = function (opts) {
|
complete (opts) {
|
||||||
var self = this
|
|
||||||
debug('send `complete`')
|
debug('send `complete`')
|
||||||
if (!opts) opts = {}
|
if (!opts) opts = {}
|
||||||
opts = self._defaultAnnounceOpts(opts)
|
opts = this._defaultAnnounceOpts(opts)
|
||||||
opts.event = 'completed'
|
opts.event = 'completed'
|
||||||
self._announce(opts)
|
this._announce(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,17 +165,15 @@ Client.prototype.complete = function (opts) {
|
|||||||
* @param {number=} opts.numwant
|
* @param {number=} opts.numwant
|
||||||
* @param {number=} opts.left (if not set, calculated automatically)
|
* @param {number=} opts.left (if not set, calculated automatically)
|
||||||
*/
|
*/
|
||||||
Client.prototype.update = function (opts) {
|
update (opts) {
|
||||||
var self = this
|
|
||||||
debug('send `update`')
|
debug('send `update`')
|
||||||
opts = self._defaultAnnounceOpts(opts)
|
opts = this._defaultAnnounceOpts(opts)
|
||||||
if (opts.event) delete opts.event
|
if (opts.event) delete opts.event
|
||||||
self._announce(opts)
|
this._announce(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.prototype._announce = function (opts) {
|
_announce (opts) {
|
||||||
var self = this
|
this._trackers.forEach(tracker => {
|
||||||
self._trackers.forEach(function (tracker) {
|
|
||||||
// tracker should not modify `opts` object, it's passed to all trackers
|
// tracker should not modify `opts` object, it's passed to all trackers
|
||||||
tracker.announce(opts)
|
tracker.announce(opts)
|
||||||
})
|
})
|
||||||
@ -245,51 +183,97 @@ Client.prototype._announce = function (opts) {
|
|||||||
* Send a scrape request to the trackers.
|
* Send a scrape request to the trackers.
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
*/
|
*/
|
||||||
Client.prototype.scrape = function (opts) {
|
scrape (opts) {
|
||||||
var self = this
|
|
||||||
debug('send `scrape`')
|
debug('send `scrape`')
|
||||||
if (!opts) opts = {}
|
if (!opts) opts = {}
|
||||||
self._trackers.forEach(function (tracker) {
|
this._trackers.forEach(tracker => {
|
||||||
// tracker should not modify `opts` object, it's passed to all trackers
|
// tracker should not modify `opts` object, it's passed to all trackers
|
||||||
tracker.scrape(opts)
|
tracker.scrape(opts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.prototype.setInterval = function (intervalMs) {
|
setInterval (intervalMs) {
|
||||||
var self = this
|
|
||||||
debug('setInterval %d', intervalMs)
|
debug('setInterval %d', intervalMs)
|
||||||
self._trackers.forEach(function (tracker) {
|
this._trackers.forEach(tracker => {
|
||||||
tracker.setInterval(intervalMs)
|
tracker.setInterval(intervalMs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.prototype.destroy = function (cb) {
|
destroy (cb) {
|
||||||
var self = this
|
if (this.destroyed) return
|
||||||
if (self.destroyed) return
|
this.destroyed = true
|
||||||
self.destroyed = true
|
|
||||||
debug('destroy')
|
debug('destroy')
|
||||||
|
|
||||||
var tasks = self._trackers.map(function (tracker) {
|
const tasks = this._trackers.map(tracker => cb => {
|
||||||
return function (cb) {
|
|
||||||
tracker.destroy(cb)
|
tracker.destroy(cb)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
parallel(tasks, cb)
|
parallel(tasks, cb)
|
||||||
|
|
||||||
self._trackers = []
|
this._trackers = []
|
||||||
self._getAnnounceOpts = null
|
this._getAnnounceOpts = null
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.prototype._defaultAnnounceOpts = function (opts) {
|
_defaultAnnounceOpts (opts = {}) {
|
||||||
var self = this
|
|
||||||
if (!opts) opts = {}
|
|
||||||
|
|
||||||
if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS
|
if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS
|
||||||
|
|
||||||
if (opts.uploaded == null) opts.uploaded = 0
|
if (opts.uploaded == null) opts.uploaded = 0
|
||||||
if (opts.downloaded == null) opts.downloaded = 0
|
if (opts.downloaded == null) opts.downloaded = 0
|
||||||
|
|
||||||
if (self._getAnnounceOpts) opts = Object.assign({}, opts, self._getAnnounceOpts())
|
if (this._getAnnounceOpts) opts = Object.assign({}, opts, this._getAnnounceOpts())
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple convenience function to scrape a tracker for an info hash without needing to
|
||||||
|
* create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
|
||||||
|
* torrents at the same time.
|
||||||
|
* @params {Object} opts
|
||||||
|
* @param {string|Array.<string>} opts.infoHash
|
||||||
|
* @param {string} opts.announce
|
||||||
|
* @param {function} cb
|
||||||
|
*/
|
||||||
|
Client.scrape = (opts, cb) => {
|
||||||
|
cb = once(cb)
|
||||||
|
|
||||||
|
if (!opts.infoHash) throw new Error('Option `infoHash` is required')
|
||||||
|
if (!opts.announce) throw new Error('Option `announce` is required')
|
||||||
|
|
||||||
|
const clientOpts = Object.assign({}, opts, {
|
||||||
|
infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash,
|
||||||
|
peerId: Buffer.from('01234567890123456789'), // dummy value
|
||||||
|
port: 6881 // dummy value
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = new Client(clientOpts)
|
||||||
|
client.once('error', cb)
|
||||||
|
client.once('warning', cb)
|
||||||
|
|
||||||
|
let len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1
|
||||||
|
const results = {}
|
||||||
|
client.on('scrape', data => {
|
||||||
|
len -= 1
|
||||||
|
results[data.infoHash] = data
|
||||||
|
if (len === 0) {
|
||||||
|
client.destroy()
|
||||||
|
const keys = Object.keys(results)
|
||||||
|
if (keys.length === 1) {
|
||||||
|
cb(null, results[keys[0]])
|
||||||
|
} else {
|
||||||
|
cb(null, results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
opts.infoHash = Array.isArray(opts.infoHash)
|
||||||
|
? opts.infoHash.map(infoHash => {
|
||||||
|
return Buffer.from(infoHash, 'hex')
|
||||||
|
})
|
||||||
|
: Buffer.from(opts.infoHash, 'hex')
|
||||||
|
client.scrape({ infoHash: opts.infoHash })
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Client
|
||||||
|
4
index.js
4
index.js
@ -1,5 +1,5 @@
|
|||||||
var Client = require('./client')
|
const Client = require('./client')
|
||||||
var Server = require('./server')
|
const Server = require('./server')
|
||||||
|
|
||||||
module.exports = Client
|
module.exports = Client
|
||||||
module.exports.Client = Client
|
module.exports.Client = Client
|
||||||
|
@ -11,9 +11,9 @@ const Tracker = require('./tracker')
|
|||||||
// boost, and saves browser resources.
|
// boost, and saves browser resources.
|
||||||
const socketPool = {}
|
const socketPool = {}
|
||||||
|
|
||||||
const RECONNECT_MINIMUM = 15 * 1000
|
const RECONNECT_MINIMUM = 10 * 1000
|
||||||
const RECONNECT_MAXIMUM = 30 * 60 * 1000
|
const RECONNECT_MAXIMUM = 30 * 60 * 1000
|
||||||
const RECONNECT_VARIANCE = 30 * 1000
|
const RECONNECT_VARIANCE = 2 * 60 * 1000
|
||||||
const OFFER_TIMEOUT = 50 * 1000
|
const OFFER_TIMEOUT = 50 * 1000
|
||||||
|
|
||||||
class WebSocketTracker extends Tracker {
|
class WebSocketTracker extends Tracker {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"bittorrent-peerid": "^1.0.2",
|
"bittorrent-peerid": "^1.0.2",
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.4.0",
|
||||||
"compact2string": "^1.2.0",
|
"compact2string": "^1.2.0",
|
||||||
"debug": "^3.1.0",
|
"debug": "^4.0.1",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"ip": "^1.0.1",
|
"ip": "^1.0.1",
|
||||||
"lru": "^3.0.0",
|
"lru": "^3.0.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user