bittorrent-tracker/client.js
Feross Aboukhadijeh 03bed33457 add webtorrent (websocket) tracker server
This PR merges webtorrent-tracker into this repo. Keeping the code in
sync between the two repos was becoming burdensome. This change should
not effect performance of the server since the webtorrent tracker is
disabled by default.

To enable the webtorrent tracker (disabled by default), do:

```js
var server = new Server({ ws: true })
```
2015-03-27 16:19:06 +13:00

224 lines
6.3 KiB
JavaScript

module.exports = Client
var debug = require('debug')('bittorrent-tracker')
var EventEmitter = require('events').EventEmitter
var inherits = require('inherits')
var once = require('once')
var url = require('url')
var common = require('./lib/common')
var HTTPTracker = require('./lib/http-tracker') // empty object in browser
var UDPTracker = require('./lib/udp-tracker') // empty object in browser
var WebSocketTracker = require('./lib/websocket-tracker')
inherits(Client, EventEmitter)
/**
* BitTorrent tracker client.
*
* Find torrent peers, to help a torrent client participate in a torrent swarm.
*
* @param {string} peerId peer id
* @param {Number} port torrent client listening port
* @param {Object} torrent parsed torrent
* @param {Object} opts options object
* @param {Number} opts.numWant number of peers to request
* @param {Number} opts.interval announce interval (in ms)
* @param {Number} opts.rtcConfig RTCPeerConnection configuration object
*/
function Client (peerId, port, torrent, opts) {
var self = this
if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
EventEmitter.call(self)
if (!opts) opts = {}
// required
self._peerId = Buffer.isBuffer(peerId)
? peerId
: new Buffer(peerId, 'hex')
self._port = port
self._infoHash = Buffer.isBuffer(torrent.infoHash)
? torrent.infoHash
: new Buffer(torrent.infoHash, 'hex')
self.torrentLength = torrent.length
// optional
self._numWant = opts.numWant || common.DEFAULT_ANNOUNCE_PEERS
self._intervalMs = opts.interval || common.DEFAULT_ANNOUNCE_INTERVAL
debug('new client %s', self._infoHash.toString('hex'))
if (typeof torrent.announce === 'string') torrent.announce = [ torrent.announce ]
if (torrent.announce == null) torrent.announce = []
self._trackers = torrent.announce
.map(function (announceUrl) {
var trackerOpts = { interval: self._intervalMs }
var protocol = url.parse(announceUrl).protocol
if ((protocol === 'http:' || protocol === 'https:') &&
typeof HTTPTracker === 'function') {
return new HTTPTracker(self, announceUrl, trackerOpts)
} else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
return new UDPTracker(self, announceUrl, trackerOpts)
} else if (protocol === 'ws:' || protocol === 'wss:') {
return new WebSocketTracker(self, announceUrl, trackerOpts)
}
return null
})
.filter(Boolean)
}
/**
* Simple convenience function to scrape a tracker for an infoHash without
* needing to create a Client, pass it a parsed torrent, etc.
* @param {string} announceUrl
* @param {string} infoHash
* @param {function} cb
*/
Client.scrape = function (announceUrl, infoHash, cb) {
cb = once(cb)
var peerId = new Buffer('01234567890123456789') // dummy value
var port = 6881 // dummy value
var torrent = {
infoHash: infoHash,
announce: [ announceUrl ]
}
var client = new Client(peerId, port, torrent)
client.once('error', cb)
client.once('scrape', function (data) {
cb(null, data)
})
client.scrape()
}
/**
* 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)
*/
Client.prototype.start = function (opts) {
var self = this
debug('send `start`')
opts = self._defaultAnnounceOpts(opts)
opts.event = 'started'
self._announce(opts)
// start announcing on intervals
self._trackers.forEach(function (tracker) {
tracker.setInterval(self._intervalMs)
})
}
/**
* 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)
*/
Client.prototype.stop = function (opts) {
var self = this
debug('send `stop`')
opts = self._defaultAnnounceOpts(opts)
opts.event = 'stopped'
self._announce(opts)
self.destroy()
}
/**
* 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) {
var self = this
debug('send `complete`')
if (!opts) opts = {}
if (opts.downloaded == null && self.torrentLength != null) {
opts.downloaded = self.torrentLength
}
opts = self._defaultAnnounceOpts(opts)
opts.event = 'completed'
self._announce(opts)
}
/**
* 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) {
var self = this
debug('send `update`')
opts = self._defaultAnnounceOpts(opts)
if (opts.event) delete opts.event
self._announce(opts)
}
Client.prototype._announce = function (opts) {
var self = this
self._trackers.forEach(function (tracker) {
tracker.announce(opts)
})
}
/**
* Send a scrape request 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.scrape = function (opts) {
var self = this
debug('send `scrape`')
if (!opts) opts = {}
self._trackers.forEach(function (tracker) {
tracker.scrape(opts)
})
}
Client.prototype.setInterval = function (intervalMs) {
var self = this
debug('setInterval')
self._intervalMs = intervalMs
self._trackers.forEach(function (tracker) {
tracker.setInterval(intervalMs)
})
}
Client.prototype.destroy = function () {
var self = this
debug('destroy')
self._trackers.forEach(function (tracker) {
if (tracker.destroy) tracker.destroy()
tracker.setInterval(0) // stop announcing on intervals
})
}
Client.prototype._defaultAnnounceOpts = function (opts) {
var self = this
if (!opts) opts = {}
if (opts.numWant == null) opts.numWant = self._numWant
if (opts.uploaded == null) opts.uploaded = 0
if (opts.downloaded == null) opts.downloaded = 0
if (opts.left == null && self.torrentLength != null) {
opts.left = self.torrentLength - opts.downloaded
}
return opts
}