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 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(opts.id || randomBytes(32))
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 = opts.id ? !!opts.ephemeral : true
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
- const id = peer.id(host, port)
78
- return new this({ port, id, ephemeral: false, firewalled: false, anyPort: false, bootstrap: [], ...opts })
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._nat.add(to.host, to.port)
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._nat.add(to.host, to.port)
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._nat.add(node.to.host, node.to.port)
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 && !this.ephemeral) {
356
- this.ephemeral = true
357
- this.io.ephemeral = true
358
- this.emit('ephemeral')
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 (err) {
165
- await serverSocket.close()
166
- await clientSocket.close()
167
- throw err
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dht-rpc",
3
- "version": "6.6.3",
3
+ "version": "6.8.0",
4
4
  "description": "Make RPC calls over a Kademlia based DHT",
5
5
  "main": "index.js",
6
6
  "files": [