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 * @param {Number} opts.wrtc custom webrtc implementation */ 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 self._rtcConfig = opts.rtcConfig self._wrtc = opts.wrtc // 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')) var trackerOpts = { interval: self._intervalMs } var webrtcSupport = !!self._wrtc || typeof window !== 'undefined' var announce = (typeof torrent.announce === 'string') ? [ torrent.announce ] : torrent.announce == null ? [] : torrent.announce self._trackers = announce .map(function (announceUrl) { 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:') && webrtcSupport) { return new WebSocketTracker(self, announceUrl, trackerOpts) } return null }) .filter(Boolean) } /** * 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.} 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: Array.isArray(infoHash) ? infoHash[0] : infoHash, announce: [ announceUrl ] } var client = new Client(peerId, port, torrent) client.once('error', cb) 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) } } }) infoHash = Array.isArray(infoHash) ? infoHash.map(function (infoHash) { return new Buffer(infoHash, 'hex') }) : new Buffer(infoHash, 'hex') client.scrape({ infoHash: infoHash }) } /** * 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 }) self._trackers = [] } 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 }