1+ exports . Client = Client
2+ // TODO: exports.Server = Server
3+ // TODO: support connecting to UDP trackers (http://www.bittorrent.org/beps/bep_0015.html)
4+
5+ var bncode = require ( 'bncode' )
6+ var compact2string = require ( 'compact2string' )
7+ var EventEmitter = require ( 'events' ) . EventEmitter
8+ var extend = require ( 'extend.js' )
9+ var http = require ( 'http' )
10+ var inherits = require ( 'inherits' )
11+ var querystring = require ( 'querystring' )
12+
13+ inherits ( Client , EventEmitter )
14+
15+ function Client ( peerId , port , torrent , opts ) {
16+ var self = this
17+ if ( ! ( self instanceof Client ) ) return new Client ( peerId , port , torrent )
18+ EventEmitter . call ( self )
19+ self . _opts = opts || { }
20+
21+ // required
22+ self . _peerId = peerId
23+ self . _port = port
24+ self . _infoHash = torrent . infoHash
25+ self . _torrentLength = torrent . length
26+ self . _announce = torrent . announce
27+
28+ // optional
29+ self . _numWant = self . _opts . numWant || 80
30+ self . _intervalMs = self . _opts . interval || ( 30 * 60 * 1000 ) // default: 30 minutes
31+
32+ self . _interval = null
33+ }
34+
35+ Client . prototype . start = function ( opts ) {
36+ var self = this
37+ opts = opts || { }
38+ opts . event = 'started'
39+ self . _request ( opts )
40+
41+ self . setInterval ( self . _intervalMs ) // start announcing on intervals
42+ }
43+
44+ Client . prototype . stop = function ( opts ) {
45+ var self = this
46+ opts = opts || { }
47+ opts . event = 'stopped'
48+ self . _request ( opts )
49+
50+ self . setInterval ( 0 ) // stop announcing on intervals
51+ }
52+
53+ Client . prototype . complete = function ( opts ) {
54+ var self = this
55+ opts = opts || { }
56+ opts . event = 'completed'
57+ self . _request ( opts )
58+ }
59+
60+ Client . prototype . update = function ( opts ) {
61+ var self = this
62+ opts = opts || { }
63+ self . _request ( opts )
64+ }
65+
66+ Client . prototype . setInterval = function ( intervalMs ) {
67+ var self = this
68+ if ( self . _interval ) {
69+ clearInterval ( self . _interval )
70+ }
71+
72+ self . _intervalMs = intervalMs
73+ if ( self . _intervalMs ) {
74+ self . _interval = setInterval ( self . update . bind ( self ) , self . _intervalMs )
75+ }
76+ }
77+
78+ /**
79+ * Send a request to the tracker
80+ */
81+ Client . prototype . _request = function ( opts ) {
82+ var self = this
83+ opts = extend ( {
84+ info_hash : bytewiseEncodeURIComponent ( self . _infoHash ) ,
85+ peer_id : bytewiseEncodeURIComponent ( self . _peerId ) ,
86+ port : self . _port ,
87+ left : self . _torrentLength - ( opts . downloaded || 0 ) ,
88+ compact : 1 ,
89+ numwant : self . _numWant ,
90+ uploaded : 0 , // default, user should provide real value
91+ downloaded : 0 // default, user should provide real value
92+ } , opts )
93+
94+ if ( self . _trackerId ) {
95+ opts . trackerid = self . _trackerId
96+ }
97+
98+ var q = querystring . stringify ( opts )
99+
100+ self . _announce . forEach ( function ( announce ) {
101+ var url = announce + '?' + q
102+ var req = http . get ( url , function ( res ) {
103+ var data = ''
104+ if ( res . statusCode !== 200 ) {
105+ res . resume ( ) // consume the whole stream
106+ self . emit ( 'error' , new Error ( 'Invalid response code ' + res . statusCode + ' from tracker' ) )
107+ return
108+ }
109+ res . on ( 'data' , function ( chunk ) {
110+ data += chunk
111+ } )
112+ res . on ( 'end' , function ( ) {
113+ self . _handleResponse ( data , announce )
114+ } )
115+ } )
116+
117+ req . on ( 'error' , function ( err ) {
118+ self . emit ( 'error' , req )
119+ } )
120+ } )
121+ }
122+
123+ Client . prototype . _handleResponse = function ( data , announce ) {
124+ var self = this
125+
126+ try {
127+ data = bncode . decode ( data )
128+ } catch ( err ) {
129+ return self . emit ( 'error' , new Error ( 'Error decoding tracker response: ' + err . message ) )
130+ }
131+ var failure = data [ 'failure reason' ]
132+ if ( failure ) {
133+ return self . emit ( 'error' , new Error ( failure ) )
134+ }
135+
136+ var warning = data [ 'warning message' ]
137+ if ( warning ) {
138+ console . warn ( warning )
139+ }
140+
141+ var interval = data . interval || data [ 'min interval' ]
142+ if ( interval && ! self . _opts . interval && self . _intervalMs !== 0 ) {
143+ // use the interval the tracker recommends, UNLESS the user manually specifies an
144+ // interval they want to use
145+ self . setInterval ( interval )
146+ }
147+
148+ var trackerId = data [ 'tracker id' ]
149+ if ( trackerId ) {
150+ // If absent, do not discard previous trackerId value
151+ self . _trackerId = trackerId
152+ }
153+
154+ self . emit ( 'update' , {
155+ announce : announce ,
156+ complete : data . complete ,
157+ incomplete : data . incomplete
158+ } )
159+
160+ if ( Buffer . isBuffer ( data . peers ) ) {
161+ // tracker returned compact response
162+ var addrs = compact2string . multi ( data . peers )
163+ addrs . forEach ( function ( addr ) {
164+ self . emit ( 'peer' , addr )
165+ } )
166+ } else if ( Array . isArray ( data . peers ) ) {
167+ // tracker returned normal response
168+ data . peers . forEach ( function ( peer ) {
169+ var ip = peer . ip
170+ self . emit ( 'peer' , ip [ 0 ] + '.' + ip [ 1 ] + '.' + ip [ 2 ] + '.' + ip [ 3 ] + ':' + peer . port )
171+ } )
172+ }
173+ }
174+
175+ function bytewiseEncodeURIComponent ( buf ) {
176+ if ( ! Buffer . isBuffer ( buf ) ) {
177+ buf = new Buffer ( buf , 'hex' )
178+ }
179+ return escape ( buf . toString ( 'binary' ) )
180+ }
0 commit comments