dht-rpc 6.6.3 → 6.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/index.js +60 -16
- package/lib/errors.js +6 -0
- package/lib/io.js +50 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,9 @@ it will switch from persistent mode to ephemeral again.
|
|
|
178
178
|
|
|
179
179
|
Emitted when the network interfaces of the computer change.
|
|
180
180
|
|
|
181
|
+
#### `node.on('nat-update', (host, port) => {})`
|
|
182
|
+
Emitted when `node.host` or `node.port` were changed.
|
|
183
|
+
|
|
181
184
|
#### `node.on('close')`
|
|
182
185
|
|
|
183
186
|
Will be emitted after `node.destroy()` is completed.
|
|
@@ -340,6 +343,14 @@ Get the routing table peers out as an array of `{ host, port }`
|
|
|
340
343
|
|
|
341
344
|
Manually add a node to the routing table.
|
|
342
345
|
|
|
346
|
+
#### `await node.suspend()`
|
|
347
|
+
|
|
348
|
+
Tell the DHT you are going to background (ie suspend and allow it to make preperations for that)
|
|
349
|
+
|
|
350
|
+
#### `await node.resume()`
|
|
351
|
+
|
|
352
|
+
Tell the DHT you are resuming from suspension.
|
|
353
|
+
|
|
343
354
|
## License
|
|
344
355
|
|
|
345
356
|
MIT
|
package/index.js
CHANGED
|
@@ -27,7 +27,7 @@ class DHT extends EventEmitter {
|
|
|
27
27
|
super()
|
|
28
28
|
|
|
29
29
|
this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
|
|
30
|
-
this.table = new Table(
|
|
30
|
+
this.table = new Table(randomBytes(32))
|
|
31
31
|
this.nodes = new TOS()
|
|
32
32
|
this.udx = opts.udx || new UDX()
|
|
33
33
|
this.io = new IO(this.table, this.udx, {
|
|
@@ -39,10 +39,11 @@ class DHT extends EventEmitter {
|
|
|
39
39
|
|
|
40
40
|
this.concurrency = opts.concurrency || 10
|
|
41
41
|
this.bootstrapped = false
|
|
42
|
-
this.ephemeral =
|
|
42
|
+
this.ephemeral = true
|
|
43
43
|
this.firewalled = this.io.firewalled
|
|
44
44
|
this.adaptive = typeof opts.ephemeral !== 'boolean' && opts.adaptive !== false
|
|
45
45
|
this.destroyed = false
|
|
46
|
+
this.suspended = false
|
|
46
47
|
|
|
47
48
|
this._nat = new NatSampler()
|
|
48
49
|
this._quickFirewall = opts.quickFirewall !== false
|
|
@@ -63,7 +64,6 @@ class DHT extends EventEmitter {
|
|
|
63
64
|
|
|
64
65
|
this.table.on('row', this._onrow)
|
|
65
66
|
|
|
66
|
-
if (this.ephemeral === false) this.io.ephemeral = false
|
|
67
67
|
this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
|
|
68
68
|
|
|
69
69
|
if (opts.nodes) {
|
|
@@ -74,8 +74,12 @@ class DHT extends EventEmitter {
|
|
|
74
74
|
static bootstrapper (port, host, opts) {
|
|
75
75
|
if (!port) throw new Error('Port is required')
|
|
76
76
|
if (!host) throw new Error('Host is required')
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
if (host === '0.0.0.0' || host === '::') throw new Error('Invalid host')
|
|
78
|
+
if (!UDX.isIPv4(host)) throw new Error('Host must be a IPv4 address')
|
|
79
|
+
|
|
80
|
+
const dht = new this({ port, ephemeral: false, firewalled: false, anyPort: false, bootstrap: [], ...opts })
|
|
81
|
+
dht._nat.add(host, port)
|
|
82
|
+
return dht
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
get id () {
|
|
@@ -102,6 +106,23 @@ class DHT extends EventEmitter {
|
|
|
102
106
|
return this.io.bind()
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
async suspend () {
|
|
110
|
+
await this.io.bind()
|
|
111
|
+
if (this.suspended || this.destroyed) return
|
|
112
|
+
this.suspended = false
|
|
113
|
+
this.io.suspend()
|
|
114
|
+
this.emit('suspend')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async resume () {
|
|
118
|
+
if (!this.suspended || this.destroyed) return
|
|
119
|
+
this.suspended = false
|
|
120
|
+
this._onwakeup()
|
|
121
|
+
await this.io.resume()
|
|
122
|
+
this.refresh()
|
|
123
|
+
this.emit('resume')
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
address () {
|
|
106
127
|
const socket = this.socket
|
|
107
128
|
return socket ? socket.address() : null
|
|
@@ -173,12 +194,12 @@ class DHT extends EventEmitter {
|
|
|
173
194
|
|
|
174
195
|
if (opts && opts.size && opts.size > 0) value = b4a.alloc(opts.size)
|
|
175
196
|
|
|
176
|
-
const req = this.io.createRequest({ id: null, host, port }, null, true, PING, null, value, (opts && opts.session) || null)
|
|
197
|
+
const req = this.io.createRequest({ id: null, host, port }, null, true, PING, null, value, (opts && opts.session) || null, (opts && opts.ttl))
|
|
177
198
|
return this._requestToPromise(req, opts)
|
|
178
199
|
}
|
|
179
200
|
|
|
180
201
|
request ({ token = null, command, target = null, value = null }, { host, port }, opts) {
|
|
181
|
-
const req = this.io.createRequest({ id: null, host, port }, token, false, command, target, value, (opts && opts.session) || null)
|
|
202
|
+
const req = this.io.createRequest({ id: null, host, port }, token, false, command, target, value, (opts && opts.session) || null, (opts && opts.ttl))
|
|
182
203
|
return this._requestToPromise(req, opts)
|
|
183
204
|
}
|
|
184
205
|
|
|
@@ -267,13 +288,24 @@ class DHT extends EventEmitter {
|
|
|
267
288
|
return req
|
|
268
289
|
}
|
|
269
290
|
|
|
291
|
+
_natAdd (host, port) {
|
|
292
|
+
const prevHost = this._nat.host
|
|
293
|
+
const prevPort = this._nat.port
|
|
294
|
+
|
|
295
|
+
this._nat.add(host, port)
|
|
296
|
+
|
|
297
|
+
if (prevHost === this._nat.host && prevPort === this._nat.port) return
|
|
298
|
+
|
|
299
|
+
this.emit('nat-update', this._nat.host, this._nat.port)
|
|
300
|
+
}
|
|
301
|
+
|
|
270
302
|
// we don't check that this is a bootstrap node but we limit the sample size to very few nodes, so fine
|
|
271
303
|
_sampleBootstrapMaybe (from, to) {
|
|
272
304
|
if (this._nonePersistentSamples.length >= Math.max(1, this.bootstrapNodes.length)) return
|
|
273
305
|
const id = from.host + ':' + from.port
|
|
274
306
|
if (this._nonePersistentSamples.indexOf(id) > -1) return
|
|
275
307
|
this._nonePersistentSamples.push(id)
|
|
276
|
-
this.
|
|
308
|
+
this._natAdd(to.host, to.port)
|
|
277
309
|
}
|
|
278
310
|
|
|
279
311
|
_addNodeFromNetwork (sample, from, to) {
|
|
@@ -293,7 +325,7 @@ class DHT extends EventEmitter {
|
|
|
293
325
|
if (sample && (oldNode.sampled === 0 || (this._tick - oldNode.sampled) >= OLD_NODE)) {
|
|
294
326
|
oldNode.to = to
|
|
295
327
|
oldNode.sampled = this._tick
|
|
296
|
-
this.
|
|
328
|
+
this._natAdd(to.host, to.port)
|
|
297
329
|
}
|
|
298
330
|
|
|
299
331
|
oldNode.pinged = oldNode.seen = this._tick
|
|
@@ -326,7 +358,7 @@ class DHT extends EventEmitter {
|
|
|
326
358
|
|
|
327
359
|
if (node.to && node.sampled === 0) {
|
|
328
360
|
node.sampled = this._tick
|
|
329
|
-
this.
|
|
361
|
+
this._natAdd(node.to.host, node.to.port)
|
|
330
362
|
}
|
|
331
363
|
|
|
332
364
|
this.emit('add-node', node)
|
|
@@ -352,10 +384,15 @@ class DHT extends EventEmitter {
|
|
|
352
384
|
this._refreshTicks = 1 // triggers a refresh next tick (allow network time to wake up also)
|
|
353
385
|
this._lastHost = null // clear network cache check
|
|
354
386
|
|
|
355
|
-
if (this.adaptive
|
|
356
|
-
this.
|
|
357
|
-
this.io.
|
|
358
|
-
|
|
387
|
+
if (this.adaptive) {
|
|
388
|
+
this.firewalled = true
|
|
389
|
+
this.io.firewalled = true
|
|
390
|
+
|
|
391
|
+
if (!this.ephemeral) {
|
|
392
|
+
this.ephemeral = true
|
|
393
|
+
this.io.ephemeral = true
|
|
394
|
+
this.emit('ephemeral')
|
|
395
|
+
}
|
|
359
396
|
}
|
|
360
397
|
|
|
361
398
|
this.emit('wakeup')
|
|
@@ -510,7 +547,7 @@ class DHT extends EventEmitter {
|
|
|
510
547
|
_ontick () {
|
|
511
548
|
const time = Date.now()
|
|
512
549
|
|
|
513
|
-
if (time - this._lastTick > SLEEPING_INTERVAL) {
|
|
550
|
+
if (time - this._lastTick > SLEEPING_INTERVAL && this.suspended === false) {
|
|
514
551
|
this._onwakeup()
|
|
515
552
|
} else {
|
|
516
553
|
this._tick++
|
|
@@ -518,7 +555,7 @@ class DHT extends EventEmitter {
|
|
|
518
555
|
|
|
519
556
|
this._lastTick = time
|
|
520
557
|
|
|
521
|
-
if (!this.bootstrapped) return
|
|
558
|
+
if (!this.bootstrapped || this.suspended) return
|
|
522
559
|
|
|
523
560
|
if (this.adaptive && this.ephemeral && --this._stableTicks <= 0) {
|
|
524
561
|
if (this._lastHost === this._nat.host) { // do not recheck the same network...
|
|
@@ -574,8 +611,15 @@ class DHT extends EventEmitter {
|
|
|
574
611
|
}
|
|
575
612
|
|
|
576
613
|
if (natSampler !== this._nat) {
|
|
614
|
+
const prevHost = this._nat.host
|
|
615
|
+
const prevPort = this._nat.port
|
|
616
|
+
|
|
577
617
|
this._nonePersistentSamples = []
|
|
578
618
|
this._nat = natSampler
|
|
619
|
+
|
|
620
|
+
if (prevHost !== this._nat.host || prevPort !== this._nat.port) {
|
|
621
|
+
this.emit('nat-update', this._nat.host, this._nat.port)
|
|
622
|
+
}
|
|
579
623
|
}
|
|
580
624
|
|
|
581
625
|
// TODO: we should make this a bit more defensive in terms of using more
|
package/lib/errors.js
CHANGED
|
@@ -7,6 +7,12 @@ exports.createTimeoutError = () => {
|
|
|
7
7
|
return timeoutErr
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
exports.createSuspendError = () => {
|
|
11
|
+
const suspendErr = new Error('Suspended')
|
|
12
|
+
suspendErr.code = 'ESUSPENDED'
|
|
13
|
+
return suspendErr
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
exports.createDestroyedError = () => {
|
|
11
17
|
const destroyErr = new Error('Request destroyed')
|
|
12
18
|
destroyErr.code = 'EDESTROYED'
|
package/lib/io.js
CHANGED
|
@@ -37,6 +37,8 @@ module.exports = class IO {
|
|
|
37
37
|
this._port = port
|
|
38
38
|
this._host = host
|
|
39
39
|
this._anyPort = anyPort !== false
|
|
40
|
+
this._boundServerPort = 0
|
|
41
|
+
this._boundClientPort = 0
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
onmessage (socket, buffer, { host, port }) {
|
|
@@ -108,7 +110,10 @@ module.exports = class IO {
|
|
|
108
110
|
async _destroy () {
|
|
109
111
|
// simplifies timing to await the bind here also, although it might be unneeded
|
|
110
112
|
await this.bind()
|
|
113
|
+
await this._clear(false)
|
|
114
|
+
}
|
|
111
115
|
|
|
116
|
+
async _clear (suspended) {
|
|
112
117
|
if (this._drainInterval) {
|
|
113
118
|
clearInterval(this._drainInterval)
|
|
114
119
|
this._drainInterval = null
|
|
@@ -122,14 +127,35 @@ module.exports = class IO {
|
|
|
122
127
|
|
|
123
128
|
if (req.session) req.session._detach(req)
|
|
124
129
|
|
|
125
|
-
req.onerror(errors.createDestroyedError(), req)
|
|
130
|
+
req.onerror(suspended ? errors.createSuspendError() : errors.createDestroyedError(), req)
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
await Promise.allSettled([
|
|
129
134
|
this.serverSocket.close(),
|
|
130
|
-
this.clientSocket.close()
|
|
131
|
-
this.networkInterfaces.destroy()
|
|
135
|
+
this.clientSocket.close()
|
|
132
136
|
])
|
|
137
|
+
|
|
138
|
+
if (!suspended) this.networkInterfaces.destroy()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async _rebind (binding) {
|
|
142
|
+
if (binding) await binding
|
|
143
|
+
if (this._destroying) return this._destroying
|
|
144
|
+
await this._clear(true)
|
|
145
|
+
await this._bindSockets()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
suspend () {
|
|
149
|
+
if (this._drainInterval) {
|
|
150
|
+
clearInterval(this._drainInterval)
|
|
151
|
+
this._drainInterval = null
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
resume () {
|
|
156
|
+
const binding = this._binding
|
|
157
|
+
this._binding = this._rebind(binding)
|
|
158
|
+
return this._binding
|
|
133
159
|
}
|
|
134
160
|
|
|
135
161
|
bind () {
|
|
@@ -142,7 +168,7 @@ module.exports = class IO {
|
|
|
142
168
|
const serverSocket = this.udx.createSocket()
|
|
143
169
|
|
|
144
170
|
try {
|
|
145
|
-
serverSocket.bind(this._port, this._host)
|
|
171
|
+
serverSocket.bind(this._boundServerPort || this._port, this._host)
|
|
146
172
|
} catch (err) {
|
|
147
173
|
if (!this._anyPort) {
|
|
148
174
|
await serverSocket.close()
|
|
@@ -160,13 +186,20 @@ module.exports = class IO {
|
|
|
160
186
|
const clientSocket = this.udx.createSocket()
|
|
161
187
|
|
|
162
188
|
try {
|
|
163
|
-
clientSocket.bind(0, this._host)
|
|
164
|
-
} catch
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
clientSocket.bind(this._boundClientPort || 0, this._host)
|
|
190
|
+
} catch {
|
|
191
|
+
try {
|
|
192
|
+
clientSocket.bind(0, this._host)
|
|
193
|
+
} catch (err) {
|
|
194
|
+
await serverSocket.close()
|
|
195
|
+
await clientSocket.close()
|
|
196
|
+
throw err
|
|
197
|
+
}
|
|
168
198
|
}
|
|
169
199
|
|
|
200
|
+
this._boundServerPort = serverSocket.address().port
|
|
201
|
+
this._boundClientPort = clientSocket.address().port
|
|
202
|
+
|
|
170
203
|
this.clientSocket = clientSocket
|
|
171
204
|
this.serverSocket = serverSocket
|
|
172
205
|
|
|
@@ -203,7 +236,7 @@ module.exports = class IO {
|
|
|
203
236
|
}
|
|
204
237
|
}
|
|
205
238
|
|
|
206
|
-
createRequest (to, token, internal, command, target, value, session) {
|
|
239
|
+
createRequest (to, token, internal, command, target, value, session, ttl) {
|
|
207
240
|
if (this._destroying !== null) return null
|
|
208
241
|
|
|
209
242
|
if (this._tid === 65536) this._tid = 0
|
|
@@ -211,7 +244,7 @@ module.exports = class IO {
|
|
|
211
244
|
const tid = this._tid++
|
|
212
245
|
const socket = this.firewalled ? this.clientSocket : this.serverSocket
|
|
213
246
|
|
|
214
|
-
const req = new Request(this, socket, tid, null, to, token, internal, command, target, value, session)
|
|
247
|
+
const req = new Request(this, socket, tid, null, to, token, internal, command, target, value, session, ttl || 0)
|
|
215
248
|
this.inflight.push(req)
|
|
216
249
|
if (session) session._attach(req)
|
|
217
250
|
return req
|
|
@@ -219,7 +252,7 @@ module.exports = class IO {
|
|
|
219
252
|
}
|
|
220
253
|
|
|
221
254
|
class Request {
|
|
222
|
-
constructor (io, socket, tid, from, to, token, internal, command, target, value, session) {
|
|
255
|
+
constructor (io, socket, tid, from, to, token, internal, command, target, value, session, ttl) {
|
|
223
256
|
this.socket = socket
|
|
224
257
|
this.tid = tid
|
|
225
258
|
this.from = from
|
|
@@ -230,6 +263,7 @@ class Request {
|
|
|
230
263
|
this.value = value
|
|
231
264
|
this.internal = internal
|
|
232
265
|
this.session = session
|
|
266
|
+
this.ttl = ttl
|
|
233
267
|
this.index = -1
|
|
234
268
|
this.sent = 0
|
|
235
269
|
this.retries = 3
|
|
@@ -258,7 +292,7 @@ class Request {
|
|
|
258
292
|
|
|
259
293
|
if (id !== null) from.id = validateId(id, from)
|
|
260
294
|
|
|
261
|
-
return new Request(io, socket, tid, from, to, token, internal, command, target, value, null)
|
|
295
|
+
return new Request(io, socket, tid, from, to, token, internal, command, target, value, null, 0)
|
|
262
296
|
} catch {
|
|
263
297
|
return null
|
|
264
298
|
}
|
|
@@ -279,7 +313,7 @@ class Request {
|
|
|
279
313
|
relay (value, to, opts) {
|
|
280
314
|
const socket = (opts && opts.socket) || this.socket
|
|
281
315
|
const buffer = this._encodeRequest(null, value, to, socket)
|
|
282
|
-
socket.trySend(buffer, to.port, to.host)
|
|
316
|
+
socket.trySend(buffer, to.port, to.host, this.ttl)
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
send (force = false) {
|
|
@@ -304,7 +338,7 @@ class Request {
|
|
|
304
338
|
if (this.destroyed) return
|
|
305
339
|
this.sent++
|
|
306
340
|
this._io.congestion.send()
|
|
307
|
-
this.socket.trySend(this._buffer, this.to.port, this.to.host)
|
|
341
|
+
this.socket.trySend(this._buffer, this.to.port, this.to.host, this.ttl)
|
|
308
342
|
if (this._timeout) clearTimeout(this._timeout)
|
|
309
343
|
this._timeout = setTimeout(oncycle, 1000, this)
|
|
310
344
|
}
|
|
@@ -355,7 +389,7 @@ class Request {
|
|
|
355
389
|
if (error > 0) c.uint.encode(state, error)
|
|
356
390
|
if (value) c.buffer.encode(state, value)
|
|
357
391
|
|
|
358
|
-
socket.trySend(state.buffer, from.port, from.host)
|
|
392
|
+
socket.trySend(state.buffer, from.port, from.host, this.ttl)
|
|
359
393
|
}
|
|
360
394
|
|
|
361
395
|
_encodeRequest (token, value, to, socket) {
|