2014-06-07 22:10:12 +00:00
|
|
|
module.exports = Client
|
|
|
|
|
2014-08-02 20:43:06 +00:00
|
|
|
var debug = require('debug')('bittorrent-tracker')
|
2014-06-07 22:10:12 +00:00
|
|
|
var EventEmitter = require('events').EventEmitter
|
|
|
|
var inherits = require('inherits')
|
2014-07-22 05:58:13 +00:00
|
|
|
var once = require('once')
|
2014-06-07 22:10:12 +00:00
|
|
|
var url = require('url')
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
var common = require('./lib/common')
|
2015-03-24 08:52:21 +00:00
|
|
|
var HTTPTracker = require('./lib/http-tracker') // empty object in browser
|
|
|
|
var UDPTracker = require('./lib/udp-tracker') // empty object in browser
|
2015-03-24 08:01:49 +00:00
|
|
|
var WebSocketTracker = require('./lib/websocket-tracker')
|
|
|
|
|
2014-06-07 22:10:12 +00:00
|
|
|
inherits(Client, EventEmitter)
|
|
|
|
|
|
|
|
/**
|
2015-03-27 03:19:06 +00:00
|
|
|
* BitTorrent tracker client.
|
|
|
|
*
|
|
|
|
* Find torrent peers, to help a torrent client participate in a torrent swarm.
|
2014-06-07 22:10:12 +00:00
|
|
|
*
|
2015-03-24 08:01:49 +00:00
|
|
|
* @param {string} peerId peer id
|
|
|
|
* @param {Number} port torrent client listening port
|
|
|
|
* @param {Object} torrent parsed torrent
|
|
|
|
* @param {Object} opts options object
|
2014-06-07 22:10:12 +00:00
|
|
|
* @param {Number} opts.numWant number of peers to request
|
2015-03-24 08:01:49 +00:00
|
|
|
* @param {Number} opts.interval announce interval (in ms)
|
|
|
|
* @param {Number} opts.rtcConfig RTCPeerConnection configuration object
|
2015-05-04 00:21:08 +00:00
|
|
|
* @param {Number} opts.wrtc custom webrtc implementation
|
2014-06-07 22:10:12 +00:00
|
|
|
*/
|
|
|
|
function Client (peerId, port, torrent, opts) {
|
|
|
|
var self = this
|
|
|
|
if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
|
|
|
|
EventEmitter.call(self)
|
2015-03-24 08:01:49 +00:00
|
|
|
if (!opts) opts = {}
|
2014-06-07 22:10:12 +00:00
|
|
|
|
|
|
|
// required
|
|
|
|
self._peerId = Buffer.isBuffer(peerId)
|
|
|
|
? peerId
|
2014-12-12 10:02:11 +00:00
|
|
|
: new Buffer(peerId, 'hex')
|
2015-05-20 13:40:25 +00:00
|
|
|
self._peerIdHex = self._peerId.toString('hex')
|
2015-05-20 13:45:59 +00:00
|
|
|
self._peerIdBinary = self._peerId.toString('binary')
|
2015-05-20 13:40:25 +00:00
|
|
|
|
2014-06-07 22:10:12 +00:00
|
|
|
self._infoHash = Buffer.isBuffer(torrent.infoHash)
|
|
|
|
? torrent.infoHash
|
|
|
|
: new Buffer(torrent.infoHash, 'hex')
|
2015-05-20 13:45:59 +00:00
|
|
|
self._infoHashHex = self._infoHash.toString('hex')
|
|
|
|
self._infoHashBinary = self._infoHash.toString('binary')
|
|
|
|
|
|
|
|
self._port = port
|
2014-06-07 22:10:12 +00:00
|
|
|
self.torrentLength = torrent.length
|
|
|
|
|
2015-05-04 00:21:08 +00:00
|
|
|
self._rtcConfig = opts.rtcConfig
|
|
|
|
self._wrtc = opts.wrtc
|
|
|
|
|
2014-06-07 22:10:12 +00:00
|
|
|
// optional
|
2015-03-24 08:01:49 +00:00
|
|
|
self._numWant = opts.numWant || common.DEFAULT_ANNOUNCE_PEERS
|
|
|
|
self._intervalMs = opts.interval || common.DEFAULT_ANNOUNCE_INTERVAL
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-05-20 13:45:59 +00:00
|
|
|
debug('new client %s', self._infoHashHex)
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-05-04 00:21:08 +00:00
|
|
|
var trackerOpts = { interval: self._intervalMs }
|
|
|
|
var webrtcSupport = !!self._wrtc || typeof window !== 'undefined'
|
|
|
|
|
2015-05-17 05:54:45 +00:00
|
|
|
var announce = (typeof torrent.announce === 'string')
|
|
|
|
? [ torrent.announce ]
|
|
|
|
: torrent.announce == null
|
|
|
|
? []
|
|
|
|
: torrent.announce
|
|
|
|
|
|
|
|
self._trackers = announce
|
2014-08-02 21:09:44 +00:00
|
|
|
.map(function (announceUrl) {
|
2015-05-24 14:43:30 +00:00
|
|
|
announceUrl = announceUrl.toString()
|
2015-03-24 08:01:49 +00:00
|
|
|
var protocol = url.parse(announceUrl).protocol
|
|
|
|
|
2015-03-24 08:52:21 +00:00
|
|
|
if ((protocol === 'http:' || protocol === 'https:') &&
|
|
|
|
typeof HTTPTracker === 'function') {
|
2015-03-24 08:01:49 +00:00
|
|
|
return new HTTPTracker(self, announceUrl, trackerOpts)
|
2015-03-24 08:52:21 +00:00
|
|
|
} else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
|
2015-03-24 08:01:49 +00:00
|
|
|
return new UDPTracker(self, announceUrl, trackerOpts)
|
2015-05-04 00:21:08 +00:00
|
|
|
} else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
|
2015-03-24 08:01:49 +00:00
|
|
|
return new WebSocketTracker(self, announceUrl, trackerOpts)
|
|
|
|
}
|
2015-03-24 08:52:21 +00:00
|
|
|
return null
|
2014-08-02 21:09:44 +00:00
|
|
|
})
|
2015-03-24 08:52:21 +00:00
|
|
|
.filter(Boolean)
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2014-07-22 05:58:13 +00:00
|
|
|
/**
|
2015-05-02 00:36:07 +00:00
|
|
|
* 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.
|
|
|
|
* @param {string} announceUrl
|
|
|
|
* @param {string|Array.<string>} infoHash
|
2014-07-22 05:58:13 +00:00
|
|
|
* @param {function} cb
|
|
|
|
*/
|
|
|
|
Client.scrape = function (announceUrl, infoHash, cb) {
|
|
|
|
cb = once(cb)
|
2015-03-24 08:01:49 +00:00
|
|
|
|
|
|
|
var peerId = new Buffer('01234567890123456789') // dummy value
|
|
|
|
var port = 6881 // dummy value
|
|
|
|
var torrent = {
|
2015-05-02 00:36:07 +00:00
|
|
|
infoHash: Array.isArray(infoHash) ? infoHash[0] : infoHash,
|
2015-03-24 08:01:49 +00:00
|
|
|
announce: [ announceUrl ]
|
2014-07-22 05:58:13 +00:00
|
|
|
}
|
2015-03-24 08:01:49 +00:00
|
|
|
var client = new Client(peerId, port, torrent)
|
2014-07-22 05:58:13 +00:00
|
|
|
client.once('error', cb)
|
2015-05-02 00:36:07 +00:00
|
|
|
|
|
|
|
var len = Array.isArray(infoHash) ? 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)
|
|
|
|
}
|
|
|
|
}
|
2014-07-22 05:58:13 +00:00
|
|
|
})
|
2015-05-02 00:36:07 +00:00
|
|
|
|
|
|
|
infoHash = Array.isArray(infoHash)
|
|
|
|
? infoHash.map(function (infoHash) { return new Buffer(infoHash, 'hex') })
|
|
|
|
: new Buffer(infoHash, 'hex')
|
|
|
|
client.scrape({ infoHash: infoHash })
|
2014-07-22 05:58:13 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
/**
|
|
|
|
* Send a `start` announce to the trackers.
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {number=} opts.uploaded
|
|
|
|
* @param {number=} opts.downloaded
|
|
|
|
* @param {number=} opts.left (if not set, calculated automatically)
|
|
|
|
*/
|
2014-06-07 22:10:12 +00:00
|
|
|
Client.prototype.start = function (opts) {
|
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('send `start`')
|
|
|
|
opts = self._defaultAnnounceOpts(opts)
|
|
|
|
opts.event = 'started'
|
|
|
|
self._announce(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
// start announcing on intervals
|
2015-01-29 22:59:08 +00:00
|
|
|
self._trackers.forEach(function (tracker) {
|
2015-03-24 08:01:49 +00:00
|
|
|
tracker.setInterval(self._intervalMs)
|
2015-01-29 22:59:08 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2014-06-07 22:10:12 +00:00
|
|
|
/**
|
2015-03-24 08:01:49 +00:00
|
|
|
* Send a `stop` announce to the trackers.
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {number=} opts.uploaded
|
|
|
|
* @param {number=} opts.downloaded
|
|
|
|
* @param {number=} opts.left (if not set, calculated automatically)
|
2014-06-07 22:10:12 +00:00
|
|
|
*/
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype.stop = function (opts) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('send `stop`')
|
|
|
|
opts = self._defaultAnnounceOpts(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
opts.event = 'stopped'
|
2014-07-13 01:44:41 +00:00
|
|
|
self._announce(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
/**
|
|
|
|
* Send a `complete` announce to the trackers.
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {number=} opts.uploaded
|
|
|
|
* @param {number=} opts.downloaded
|
|
|
|
* @param {number=} opts.left (if not set, calculated automatically)
|
|
|
|
*/
|
|
|
|
Client.prototype.complete = function (opts) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('send `complete`')
|
|
|
|
if (!opts) opts = {}
|
|
|
|
if (opts.downloaded == null && self.torrentLength != null) {
|
|
|
|
opts.downloaded = self.torrentLength
|
|
|
|
}
|
|
|
|
opts = self._defaultAnnounceOpts(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
opts.event = 'completed'
|
2014-07-13 01:44:41 +00:00
|
|
|
self._announce(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
/**
|
|
|
|
* Send a `update` announce to the trackers.
|
|
|
|
* @param {Object} opts
|
|
|
|
* @param {number=} opts.uploaded
|
|
|
|
* @param {number=} opts.downloaded
|
|
|
|
* @param {number=} opts.left (if not set, calculated automatically)
|
|
|
|
*/
|
|
|
|
Client.prototype.update = function (opts) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('send `update`')
|
|
|
|
opts = self._defaultAnnounceOpts(opts)
|
|
|
|
if (opts.event) delete opts.event
|
2014-07-13 01:44:41 +00:00
|
|
|
self._announce(opts)
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype._announce = function (opts) {
|
2015-01-29 22:59:08 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
self._trackers.forEach(function (tracker) {
|
|
|
|
tracker.announce(opts)
|
|
|
|
})
|
2015-01-29 22:59:08 +00:00
|
|
|
}
|
|
|
|
|
2014-07-13 01:44:41 +00:00
|
|
|
/**
|
2015-03-24 08:01:49 +00:00
|
|
|
* Send a scrape request to the trackers.
|
2014-07-13 01:44:41 +00:00
|
|
|
* @param {Object} opts
|
|
|
|
* @param {number=} opts.uploaded
|
|
|
|
* @param {number=} opts.downloaded
|
|
|
|
* @param {number=} opts.left (if not set, calculated automatically)
|
|
|
|
*/
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype.scrape = function (opts) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('send `scrape`')
|
|
|
|
if (!opts) opts = {}
|
|
|
|
self._trackers.forEach(function (tracker) {
|
|
|
|
tracker.scrape(opts)
|
|
|
|
})
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype.setInterval = function (intervalMs) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('setInterval')
|
2014-06-07 22:10:12 +00:00
|
|
|
self._intervalMs = intervalMs
|
2015-03-10 03:19:27 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
self._trackers.forEach(function (tracker) {
|
|
|
|
tracker.setInterval(intervalMs)
|
2014-06-07 22:10:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype.destroy = function () {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
debug('destroy')
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
self._trackers.forEach(function (tracker) {
|
2015-05-17 06:25:34 +00:00
|
|
|
tracker.destroy()
|
2015-03-24 08:01:49 +00:00
|
|
|
tracker.setInterval(0) // stop announcing on intervals
|
2014-06-07 22:10:12 +00:00
|
|
|
})
|
2015-05-17 05:55:01 +00:00
|
|
|
self._trackers = []
|
2014-06-07 22:10:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
Client.prototype._defaultAnnounceOpts = function (opts) {
|
2014-06-07 22:10:12 +00:00
|
|
|
var self = this
|
2015-03-24 08:01:49 +00:00
|
|
|
if (!opts) opts = {}
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
if (opts.numWant == null) opts.numWant = self._numWant
|
2014-06-07 22:10:12 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
if (opts.uploaded == null) opts.uploaded = 0
|
|
|
|
if (opts.downloaded == null) opts.downloaded = 0
|
2014-07-20 11:34:32 +00:00
|
|
|
|
2015-03-24 08:01:49 +00:00
|
|
|
if (opts.left == null && self.torrentLength != null) {
|
|
|
|
opts.left = self.torrentLength - opts.downloaded
|
2014-07-20 11:34:32 +00:00
|
|
|
}
|
2015-03-24 08:01:49 +00:00
|
|
|
return opts
|
2014-07-20 11:34:32 +00:00
|
|
|
}
|