Skip to content

Commit 39507bf

Browse files
committed
Scrape implementation for websocket. Issue webtorrent#116
1 parent eb3cefe commit 39507bf

File tree

3 files changed

+101
-24
lines changed

3 files changed

+101
-24
lines changed

lib/client/websocket-tracker.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ WebSocketTracker.prototype.announce = function (opts) {
4747

4848
self._generateOffers(numwant, function (offers) {
4949
var params = extend(opts, {
50+
action: 'announce',
5051
numwant: numwant,
5152
info_hash: self.client._infoHashBinary,
5253
peer_id: self.client._peerIdBinary,
@@ -61,7 +62,21 @@ WebSocketTracker.prototype.announce = function (opts) {
6162
WebSocketTracker.prototype.scrape = function (opts) {
6263
var self = this
6364
if (self.destroyed || self.reconnecting) return
64-
self._onSocketError(new Error('scrape not supported ' + self.announceUrl))
65+
if (!self.socket.connected) {
66+
return self.socket.once('connect', self.scrape.bind(self, opts))
67+
}
68+
69+
var infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
70+
? opts.infoHash.map(function (infoHash) {
71+
return infoHash.toString('binary')
72+
})
73+
: (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary
74+
var params = {
75+
action: 'scrape',
76+
info_hash: infoHashes
77+
}
78+
79+
self._send(params)
6580
}
6681

6782
WebSocketTracker.prototype.destroy = function (onclose) {
@@ -133,6 +148,18 @@ WebSocketTracker.prototype._onSocketData = function (data) {
133148
return
134149
}
135150

151+
if (data.action === common.ACTIONS.ANNOUNCE || data.offer || data.answer) {
152+
self._onAnnounceResponse(data)
153+
} else if (data.action === common.ACTIONS.SCRAPE) {
154+
self._onScrapeResponse(data)
155+
} else {
156+
throw new Error('invalid action in WS response: ' + data.action)
157+
}
158+
}
159+
160+
WebSocketTracker.prototype._onAnnounceResponse = function (data) {
161+
var self = this
162+
136163
if (data.info_hash !== self.client._infoHashBinary) {
137164
debug(
138165
'ignoring websocket data from %s for %s (looking for %s: reused socket)',
@@ -215,6 +242,32 @@ WebSocketTracker.prototype._onSocketData = function (data) {
215242
}
216243
}
217244

245+
WebSocketTracker.prototype._onScrapeResponse = function (data) {
246+
var self = this
247+
// NOTE: the unofficial spec says to use the 'files' key, 'host' has been
248+
// seen in practice
249+
data = data.files || data.host || {}
250+
251+
var keys = Object.keys(data)
252+
if (keys.length === 0) {
253+
self.client.emit('warning', new Error('invalid scrape response'))
254+
return
255+
}
256+
257+
keys.forEach(function (infoHash) {
258+
var response = data[infoHash]
259+
// TODO: optionally handle data.flags.min_request_interval
260+
// (separate from announce interval)
261+
self.client.emit('scrape', {
262+
announce: self.announceUrl,
263+
infoHash: common.binaryToHex(infoHash),
264+
complete: response.complete,
265+
incomplete: response.incomplete,
266+
downloaded: response.downloaded
267+
})
268+
})
269+
}
270+
218271
WebSocketTracker.prototype._onSocketClose = function () {
219272
var self = this
220273
if (self.destroyed) return

lib/server/parse-websocket.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,51 @@ function parseWebSocketRequest (socket, opts, params) {
66
if (!opts) opts = {}
77
params = JSON.parse(params) // may throw
88

9-
params.action = common.ACTIONS.ANNOUNCE
109
params.type = 'ws'
1110
params.socket = socket
11+
if (params.action === 'announce' || params.answer || params.offers) {
12+
params.action = common.ACTIONS.ANNOUNCE
1213

13-
if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
14-
throw new Error('invalid info_hash')
15-
}
16-
params.info_hash = common.binaryToHex(params.info_hash)
14+
if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
15+
throw new Error('invalid info_hash')
16+
}
17+
params.info_hash = common.binaryToHex(params.info_hash)
1718

18-
if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
19-
throw new Error('invalid peer_id')
20-
}
21-
params.peer_id = common.binaryToHex(params.peer_id)
19+
if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
20+
throw new Error('invalid peer_id')
21+
}
22+
params.peer_id = common.binaryToHex(params.peer_id)
2223

23-
if (params.answer) {
24-
if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) {
25-
throw new Error('invalid `to_peer_id` (required with `answer`)')
24+
if (params.answer) {
25+
if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) {
26+
throw new Error('invalid `to_peer_id` (required with `answer`)')
27+
}
28+
params.to_peer_id = common.binaryToHex(params.to_peer_id)
2629
}
27-
params.to_peer_id = common.binaryToHex(params.to_peer_id)
28-
}
2930

30-
params.left = Number(params.left) || Infinity
31-
params.numwant = Math.min(
32-
Number(params.offers && params.offers.length) || 0, // no default - explicit only
33-
common.MAX_ANNOUNCE_PEERS
34-
)
35-
params.compact = -1 // return full peer objects (used for websocket responses)
31+
params.left = Number(params.left) || Infinity
32+
params.numwant = Math.min(
33+
Number(params.offers && params.offers.length) || 0, // no default - explicit only
34+
common.MAX_ANNOUNCE_PEERS
35+
)
36+
params.compact = -1 // return full peer objects (used for websocket responses)
37+
} else if (params.action === 'scrape') {
38+
params.action = common.ACTIONS.SCRAPE
39+
40+
if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ]
41+
if (Array.isArray(params.info_hash)) {
42+
params.info_hash = params.info_hash.map(function (binaryInfoHash) {
43+
if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) {
44+
throw new Error('invalid info_hash')
45+
}
46+
return common.binaryToHex(binaryInfoHash)
47+
})
48+
} else {
49+
params.info_hash = common.binaryToHex(params.info_hash)
50+
}
51+
} else {
52+
throw new Error('invalid action in WS request: ' + params.action)
53+
}
3654

3755
params.ip = opts.trustProxy
3856
? socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress

server.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ Server.prototype._onWebSocketRequest = function (socket, opts, params) {
327327
self._onRequest(params, function (err, response) {
328328
if (err) {
329329
socket.send(JSON.stringify({
330+
action: params.action,
330331
'failure reason': err.message,
331332
info_hash: common.hexToBinary(params.info_hash)
332333
}), socket.onSend)
@@ -336,9 +337,14 @@ Server.prototype._onWebSocketRequest = function (socket, opts, params) {
336337
}
337338
if (self.destroyed) return
338339

339-
if (socket.infoHashes.indexOf(params.info_hash) === -1) {
340-
socket.infoHashes.push(params.info_hash)
341-
}
340+
var hashes
341+
if (typeof params.info_hash === 'string') hashes = [ params.info_hash ]
342+
else hashes = params.info_hash
343+
hashes.forEach(function (info_hash) {
344+
if (socket.infoHashes.indexOf(info_hash) === -1) {
345+
socket.infoHashes.push(info_hash)
346+
}
347+
})
342348

343349
var peers = response.peers
344350
delete response.peers

0 commit comments

Comments
 (0)