UNPKG

4.28 kBJavaScriptView Raw
1const debug = require('debug')('uwt:swarm')
2const LRU = require('lru')
3const randomIterate = require('random-iterate')
4
5class Swarm {
6 constructor (infoHash, server) {
7 this.peers = new LRU({
8 max: server.peersCacheLength || 1000,
9 maxAge: server.peersCacheTtl || 600000 // 600 000ms = 10 minutes
10 })
11
12 this.peers.on('evict', data => {
13 const peer = data.value
14 this._onAnnounceStopped({}, peer, peer.peerId)
15 })
16
17 this.complete = 0
18 this.incomplete = 0
19
20 this._infoHash = infoHash
21 this._server = server
22 }
23
24 announce (params, cb) {
25 const id = params.peer_id
26 // Mark the source peer as recently used in cache
27 const peer = this.peers.get(id)
28
29 if (params.event === 'started') {
30 this._onAnnounceStarted(params, peer, id)
31 } else if (params.event === 'stopped') {
32 this._onAnnounceStopped(params, peer, id)
33 } else if (params.event === 'completed') {
34 this._onAnnounceCompleted(params, peer, id)
35 } else if (params.event === 'update') {
36 this._onAnnounceUpdate(params, peer, id)
37 } else {
38 cb(new Error('invalid event'))
39 return
40 }
41 cb(null, {
42 complete: this.complete,
43 incomplete: this.incomplete,
44 peers: this._getPeers(params.numwant, params.peer_id)
45 })
46 }
47
48 scrape (params, cb) {
49 cb(null, {
50 complete: this.complete,
51 incomplete: this.incomplete
52 })
53 }
54
55 _onAnnounceStarted (params, peer, id) {
56 if (peer) {
57 debug('unexpected `started` event from peer that is already in swarm')
58 return this._onAnnounceUpdate(params, peer, id) // treat as an update
59 }
60
61 if (params.left === 0) this.complete += 1
62 else this.incomplete += 1
63 peer = this.peers.set(id, {
64 complete: params.left === 0,
65 peerId: params.peer_id, // as hex
66 ip: params.ip,
67 port: params.port,
68 socket: params.socket
69 })
70 }
71
72 _onAnnounceStopped (params, peer, id) {
73 if (!peer) {
74 debug('unexpected `stopped` event from peer that is not in swarm')
75 return this._checkEmpty()
76 }
77
78 if (peer.complete) this.complete -= 1
79 else this.incomplete -= 1
80
81 // Remove this swarm's infohash from the list of active swarms that this peer is participating in.
82 if (peer.socket && peer.socket.infoHashes) {
83 const index = peer.socket.infoHashes.indexOf(this._infoHash)
84 if (index === -1) return debug('Unexpected index of infoHash from peer\'s infoHashes')
85 peer.socket.infoHashes.splice(index, 1)
86
87 // If it's not in any other swarms, close the websocket to conserve server resources.
88 if (peer.socket.infoHashes.length === 0) {
89 process.nextTick(() => {
90 peer.socket.close()
91 peer.socket = null
92 })
93 }
94 }
95
96 this.peers.remove(id)
97
98 this._checkEmpty()
99 }
100
101 _onAnnounceCompleted (params, peer, id) {
102 if (!peer) {
103 debug('unexpected `completed` event from peer that is not in swarm')
104 return this._onAnnounceStarted(params, peer, id) // treat as a start
105 }
106 if (peer.complete) {
107 debug('unexpected `completed` event from peer that is already marked as completed')
108 return this._onAnnounceUpdate(params, peer, id) // treat as an update
109 }
110
111 this.complete += 1
112 this.incomplete -= 1
113 peer.complete = true
114 this.peers.set(id, peer)
115 }
116
117 _onAnnounceUpdate (params, peer, id) {
118 if (!peer) {
119 debug('unexpected `update` event from peer that is not in swarm')
120 return this._onAnnounceStarted(params, peer, id) // treat as a start
121 }
122
123 if (!peer.complete && params.left === 0) {
124 this.complete += 1
125 this.incomplete -= 1
126 peer.complete = true
127 this.peers.set(id, peer)
128 }
129 }
130
131 _getPeers (numwant, ownPeerId) {
132 const peers = []
133 const ite = randomIterate(this.peers.keys)
134 let peerId
135 while ((peerId = ite()) && peers.length < numwant) {
136 // Don't mark the peer as most recently used on announce
137 const peer = this.peers.peek(peerId)
138 if (!peer) continue
139 if (peer.peerId === ownPeerId) continue // don't send peer to itself
140 peers.push(peer)
141 }
142 return peers
143 }
144
145 _checkEmpty () {
146 if (this.peers.length === 0) {
147 this._server.deleteSwarm(this._infoHash)
148 }
149 }
150}
151
152module.exports = Swarm