bittorrent-tracker/client.js
Feross Aboukhadijeh f575cfc3c9 major client refactor - each tracker type into its own file
in preparation of adding a new websocket tracker type, for webtorrent
2015-03-24 01:01:49 -07:00

222 lines
6.2 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')
var UDPTracker = require('./lib/udp-tracker')
var WebSocketTracker = require('./lib/websocket-tracker')
inherits(Client, EventEmitter)
/**
* A Client manages tracker connections for a torrent.
*
* @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 ]
self._trackers = (torrent.announce || [])
.filter(function (announceUrl) {
var protocol = url.parse(announceUrl).protocol
return [ 'udp:', 'http:', 'https:', 'ws:', 'wss:' ].indexOf(protocol) !== -1
})
.map(function (announceUrl) {
var trackerOpts = { interval: self._intervalMs }
var protocol = url.parse(announceUrl).protocol
if (protocol === 'http:' || protocol === 'https:') {
return new HTTPTracker(self, announceUrl, trackerOpts)
} else if (protocol === 'udp:') {
return new UDPTracker(self, announceUrl, trackerOpts)
} else if (protocol === 'ws:' || protocol === 'wss:') {
return new WebSocketTracker(self, announceUrl, trackerOpts)
}
})
}
/**
* 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
}