libp2p 0.35.8 → 0.36.0-d44bd90

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.
Files changed (108) hide show
  1. package/dist/index.min.js +55 -0
  2. package/dist/src/circuit/auto-relay.d.ts +8 -10
  3. package/dist/src/circuit/auto-relay.d.ts.map +1 -1
  4. package/dist/src/config.d.ts +2 -0
  5. package/dist/src/config.d.ts.map +1 -1
  6. package/dist/src/connection-manager/auto-dialler.d.ts +1 -1
  7. package/dist/src/connection-manager/auto-dialler.d.ts.map +1 -1
  8. package/dist/src/connection-manager/index.d.ts +1 -2
  9. package/dist/src/connection-manager/index.d.ts.map +1 -1
  10. package/dist/src/content-routing/utils.d.ts +3 -3
  11. package/dist/src/content-routing/utils.d.ts.map +1 -1
  12. package/dist/src/dialer/dial-request.d.ts.map +1 -1
  13. package/dist/src/dialer/index.d.ts +12 -7
  14. package/dist/src/dialer/index.d.ts.map +1 -1
  15. package/dist/src/errors.d.ts +2 -0
  16. package/dist/src/fetch/constants.d.ts +2 -0
  17. package/dist/src/fetch/constants.d.ts.map +1 -0
  18. package/dist/src/fetch/index.d.ts +76 -0
  19. package/dist/src/fetch/index.d.ts.map +1 -0
  20. package/dist/src/fetch/proto.d.ts +134 -0
  21. package/dist/src/identify/index.d.ts +4 -4
  22. package/dist/src/identify/index.d.ts.map +1 -1
  23. package/dist/src/index.d.ts +26 -7
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/metrics/index.d.ts +9 -4
  26. package/dist/src/metrics/index.d.ts.map +1 -1
  27. package/dist/src/metrics/tracked-map.d.ts +6 -1
  28. package/dist/src/metrics/tracked-map.d.ts.map +1 -1
  29. package/dist/src/peer-routing.d.ts +1 -1
  30. package/dist/src/peer-routing.d.ts.map +1 -1
  31. package/dist/src/peer-store/address-book.d.ts +30 -84
  32. package/dist/src/peer-store/address-book.d.ts.map +1 -1
  33. package/dist/src/peer-store/index.d.ts +28 -94
  34. package/dist/src/peer-store/index.d.ts.map +1 -1
  35. package/dist/src/peer-store/key-book.d.ts +31 -15
  36. package/dist/src/peer-store/key-book.d.ts.map +1 -1
  37. package/dist/src/peer-store/metadata-book.d.ts +33 -29
  38. package/dist/src/peer-store/metadata-book.d.ts.map +1 -1
  39. package/dist/src/peer-store/pb/peer.d.ts +222 -0
  40. package/dist/src/peer-store/proto-book.d.ts +27 -28
  41. package/dist/src/peer-store/proto-book.d.ts.map +1 -1
  42. package/dist/src/peer-store/store.d.ts +73 -0
  43. package/dist/src/peer-store/store.d.ts.map +1 -0
  44. package/dist/src/peer-store/types.d.ts +202 -0
  45. package/dist/src/peer-store/types.d.ts.map +1 -0
  46. package/dist/src/ping/index.d.ts +32 -25
  47. package/dist/src/ping/index.d.ts.map +1 -1
  48. package/dist/src/record/peer-record/index.d.ts +2 -2
  49. package/dist/src/record/peer-record/index.d.ts.map +1 -1
  50. package/dist/src/registrar.d.ts +5 -5
  51. package/dist/src/registrar.d.ts.map +1 -1
  52. package/dist/src/types.d.ts +90 -0
  53. package/dist/src/types.d.ts.map +1 -0
  54. package/dist/src/upgrader.d.ts +8 -2
  55. package/dist/src/upgrader.d.ts.map +1 -1
  56. package/package.json +23 -26
  57. package/src/circuit/auto-relay.js +52 -38
  58. package/src/config.js +2 -0
  59. package/src/connection-manager/auto-dialler.js +20 -8
  60. package/src/connection-manager/index.js +42 -22
  61. package/src/content-routing/utils.js +4 -4
  62. package/src/dialer/dial-request.js +4 -1
  63. package/src/dialer/index.js +38 -8
  64. package/src/errors.js +2 -0
  65. package/src/fetch/README.md +36 -0
  66. package/src/fetch/constants.js +6 -0
  67. package/src/fetch/index.js +159 -0
  68. package/src/fetch/proto.d.ts +134 -0
  69. package/src/fetch/proto.js +333 -0
  70. package/src/fetch/proto.proto +15 -0
  71. package/src/identify/index.js +47 -38
  72. package/src/index.js +81 -34
  73. package/src/metrics/index.js +15 -8
  74. package/src/metrics/tracked-map.js +40 -14
  75. package/src/peer-routing.js +4 -1
  76. package/src/peer-store/address-book.js +254 -226
  77. package/src/peer-store/index.js +79 -110
  78. package/src/peer-store/key-book.js +99 -54
  79. package/src/peer-store/metadata-book.js +171 -101
  80. package/src/peer-store/pb/peer.d.ts +222 -0
  81. package/src/peer-store/pb/peer.js +643 -0
  82. package/src/peer-store/pb/peer.proto +31 -0
  83. package/src/peer-store/proto-book.js +171 -105
  84. package/src/peer-store/store.js +263 -0
  85. package/src/peer-store/types.ts +245 -0
  86. package/src/ping/index.js +51 -46
  87. package/src/record/peer-record/index.js +1 -1
  88. package/src/record/utils.js +1 -1
  89. package/src/registrar.js +4 -4
  90. package/src/types.ts +98 -0
  91. package/src/upgrader.js +32 -2
  92. package/dist/src/peer-store/book.d.ts +0 -81
  93. package/dist/src/peer-store/book.d.ts.map +0 -1
  94. package/dist/src/peer-store/persistent/consts.d.ts +0 -6
  95. package/dist/src/peer-store/persistent/consts.d.ts.map +0 -1
  96. package/dist/src/peer-store/persistent/index.d.ts +0 -129
  97. package/dist/src/peer-store/persistent/index.d.ts.map +0 -1
  98. package/dist/src/peer-store/persistent/pb/address-book.d.ts +0 -198
  99. package/dist/src/peer-store/persistent/pb/proto-book.d.ts +0 -59
  100. package/src/peer-store/book.js +0 -124
  101. package/src/peer-store/persistent/consts.js +0 -15
  102. package/src/peer-store/persistent/index.js +0 -408
  103. package/src/peer-store/persistent/pb/address-book.d.ts +0 -198
  104. package/src/peer-store/persistent/pb/address-book.js +0 -522
  105. package/src/peer-store/persistent/pb/address-book.proto +0 -27
  106. package/src/peer-store/persistent/pb/proto-book.d.ts +0 -59
  107. package/src/peer-store/persistent/pb/proto-book.js +0 -157
  108. package/src/peer-store/persistent/pb/proto-book.proto +0 -5
@@ -87,14 +87,22 @@ class ConnectionManager extends EventEmitter {
87
87
  *
88
88
  * @type {Map<string, number>}
89
89
  */
90
- this._peerValues = trackedMap(METRICS_COMPONENT, METRICS_PEER_VALUES, this._libp2p.metrics)
90
+ this._peerValues = trackedMap({
91
+ component: METRICS_COMPONENT,
92
+ metric: METRICS_PEER_VALUES,
93
+ metrics: this._libp2p.metrics
94
+ })
91
95
 
92
96
  /**
93
97
  * Map of connections per peer
94
98
  *
95
99
  * @type {Map<string, Connection[]>}
96
100
  */
97
- this.connections = trackedMap(METRICS_COMPONENT, METRICS_PEER_CONNECTIONS, this._libp2p.metrics)
101
+ this.connections = trackedMap({
102
+ component: METRICS_COMPONENT,
103
+ metric: METRICS_PEER_CONNECTIONS,
104
+ metrics: this._libp2p.metrics
105
+ })
98
106
 
99
107
  this._started = false
100
108
  this._timer = null
@@ -104,6 +112,9 @@ class ConnectionManager extends EventEmitter {
104
112
  latencyCheckIntervalMs: this._options.pollInterval,
105
113
  dataEmitIntervalMs: this._options.pollInterval
106
114
  })
115
+
116
+ // This emitter gets listened to a lot
117
+ this.setMaxListeners(Infinity)
107
118
  }
108
119
 
109
120
  /**
@@ -187,19 +198,22 @@ class ConnectionManager extends EventEmitter {
187
198
  *
188
199
  * @private
189
200
  */
190
- _checkMetrics () {
201
+ async _checkMetrics () {
191
202
  if (this._libp2p.metrics) {
192
- const movingAverages = this._libp2p.metrics.global.movingAverages
193
- // @ts-ignore moving averages object types
194
- const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
195
- this._checkMaxLimit('maxReceivedData', received)
196
- // @ts-ignore moving averages object types
197
- const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
198
- this._checkMaxLimit('maxSentData', sent)
199
- const total = received + sent
200
- this._checkMaxLimit('maxData', total)
201
- log('metrics update', total)
202
- this._timer = retimer(this._checkMetrics, this._options.pollInterval)
203
+ try {
204
+ const movingAverages = this._libp2p.metrics.global.movingAverages
205
+ // @ts-ignore moving averages object types
206
+ const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
207
+ await this._checkMaxLimit('maxReceivedData', received)
208
+ // @ts-ignore moving averages object types
209
+ const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
210
+ await this._checkMaxLimit('maxSentData', sent)
211
+ const total = received + sent
212
+ await this._checkMaxLimit('maxData', total)
213
+ log('metrics update', total)
214
+ } finally {
215
+ this._timer = retimer(this._checkMetrics, this._options.pollInterval)
216
+ }
203
217
  }
204
218
  }
205
219
 
@@ -207,9 +221,8 @@ class ConnectionManager extends EventEmitter {
207
221
  * Tracks the incoming connection and check the connection limit
208
222
  *
209
223
  * @param {Connection} connection
210
- * @returns {void}
211
224
  */
212
- onConnect (connection) {
225
+ async onConnect (connection) {
213
226
  const peerId = connection.remotePeer
214
227
  const peerIdStr = peerId.toB58String()
215
228
  const storedConn = this.connections.get(peerIdStr)
@@ -222,13 +235,13 @@ class ConnectionManager extends EventEmitter {
222
235
  this.connections.set(peerIdStr, [connection])
223
236
  }
224
237
 
225
- this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
238
+ await this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
226
239
 
227
240
  if (!this._peerValues.has(peerIdStr)) {
228
241
  this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
229
242
  }
230
243
 
231
- this._checkMaxLimit('maxConnections', this.size)
244
+ await this._checkMaxLimit('maxConnections', this.size)
232
245
  }
233
246
 
234
247
  /**
@@ -296,6 +309,9 @@ class ConnectionManager extends EventEmitter {
296
309
  */
297
310
  _onLatencyMeasure (summary) {
298
311
  this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
312
+ .catch(err => {
313
+ log.error(err)
314
+ })
299
315
  }
300
316
 
301
317
  /**
@@ -305,12 +321,12 @@ class ConnectionManager extends EventEmitter {
305
321
  * @param {string} name - The name of the field to check limits for
306
322
  * @param {number} value - The current value of the field
307
323
  */
308
- _checkMaxLimit (name, value) {
324
+ async _checkMaxLimit (name, value) {
309
325
  const limit = this._options[name]
310
326
  log('checking limit of %s. current value: %d of %d', name, value, limit)
311
327
  if (value > limit) {
312
328
  log('%s: limit exceeded: %s, %d', this._peerId, name, value)
313
- this._maybeDisconnectOne()
329
+ await this._maybeDisconnectOne()
314
330
  }
315
331
  }
316
332
 
@@ -320,7 +336,7 @@ class ConnectionManager extends EventEmitter {
320
336
  *
321
337
  * @private
322
338
  */
323
- _maybeDisconnectOne () {
339
+ async _maybeDisconnectOne () {
324
340
  if (this._options.minConnections < this.connections.size) {
325
341
  const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1])))
326
342
  log('%s: sorted peer values: %j', this._peerId, peerValues)
@@ -331,7 +347,11 @@ class ConnectionManager extends EventEmitter {
331
347
  log('%s: closing a connection to %j', this._peerId, peerId)
332
348
  for (const connections of this.connections.values()) {
333
349
  if (connections[0].remotePeer.toB58String() === peerId) {
334
- connections[0].close()
350
+ connections[0].close().catch(err => {
351
+ log.error(err)
352
+ })
353
+ // TODO: should not need to invoke this manually
354
+ this.onDisconnect(connections[0])
335
355
  break
336
356
  }
337
357
  }
@@ -14,12 +14,12 @@ const take = require('it-take')
14
14
  * Store the multiaddrs from every peer in the passed peer store
15
15
  *
16
16
  * @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
17
- * @param {import('../peer-store')} peerStore
17
+ * @param {import('../peer-store/types').PeerStore} peerStore
18
18
  */
19
- function storeAddresses (source, peerStore) {
20
- return map(source, (peer) => {
19
+ async function * storeAddresses (source, peerStore) {
20
+ yield * map(source, async (peer) => {
21
21
  // ensure we have the addresses for a given peer
22
- peerStore.addressBook.add(peer.id, peer.multiaddrs)
22
+ await peerStore.addressBook.add(peer.id, peer.multiaddrs)
23
23
 
24
24
  return peer
25
25
  })
@@ -63,7 +63,10 @@ class DialRequest {
63
63
  tokens.forEach(token => tokenHolder.push(token))
64
64
  const dialAbortControllers = this.addrs.map(() => {
65
65
  const controller = new AbortController()
66
- setMaxListeners && setMaxListeners(Infinity, controller.signal)
66
+ try {
67
+ // fails on node < 15.4
68
+ setMaxListeners && setMaxListeners(Infinity, controller.signal)
69
+ } catch {}
67
70
 
68
71
  return controller
69
72
  })
@@ -1,6 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const debug = require('debug')
4
+ const all = require('it-all')
5
+ const filter = require('it-filter')
6
+ const { pipe } = require('it-pipe')
4
7
  const log = Object.assign(debug('libp2p:dialer'), {
5
8
  error: debug('libp2p:dialer:err')
6
9
  })
@@ -30,15 +33,17 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets'
30
33
  /**
31
34
  * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
32
35
  * @typedef {import('peer-id')} PeerId
33
- * @typedef {import('../peer-store')} PeerStore
34
- * @typedef {import('../peer-store/address-book').Address} Address
36
+ * @typedef {import('../peer-store/types').PeerStore} PeerStore
37
+ * @typedef {import('../peer-store/types').Address} Address
35
38
  * @typedef {import('../transport-manager')} TransportManager
39
+ * @typedef {import('../types').ConnectionGater} ConnectionGater
36
40
  */
37
41
 
38
42
  /**
39
43
  * @typedef {Object} DialerProperties
40
44
  * @property {PeerStore} peerStore
41
45
  * @property {TransportManager} transportManager
46
+ * @property {ConnectionGater} connectionGater
42
47
  *
43
48
  * @typedef {(addr:Multiaddr) => Promise<string[]>} Resolver
44
49
  *
@@ -70,6 +75,7 @@ class Dialer {
70
75
  constructor ({
71
76
  transportManager,
72
77
  peerStore,
78
+ connectionGater,
73
79
  addressSorter = publicAddressesFirst,
74
80
  maxParallelDials = MAX_PARALLEL_DIALS,
75
81
  maxAddrsToDial = MAX_ADDRS_TO_DIAL,
@@ -78,6 +84,7 @@ class Dialer {
78
84
  resolvers = {},
79
85
  metrics
80
86
  }) {
87
+ this.connectionGater = connectionGater
81
88
  this.transportManager = transportManager
82
89
  this.peerStore = peerStore
83
90
  this.addressSorter = addressSorter
@@ -88,10 +95,18 @@ class Dialer {
88
95
  this.tokens = [...new Array(maxParallelDials)].map((_, index) => index)
89
96
 
90
97
  /** @type {Map<string, PendingDial>} */
91
- this._pendingDials = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIALS, metrics)
98
+ this._pendingDials = trackedMap({
99
+ component: METRICS_COMPONENT,
100
+ metric: METRICS_PENDING_DIALS,
101
+ metrics
102
+ })
92
103
 
93
104
  /** @type {Map<string, { resolve: (value: any) => void, reject: (err: Error) => void}>} */
94
- this._pendingDialTargets = trackedMap(METRICS_COMPONENT, METRICS_PENDING_DIAL_TARGETS, metrics)
105
+ this._pendingDialTargets = trackedMap({
106
+ component: METRICS_COMPONENT,
107
+ metric: METRICS_PENDING_DIAL_TARGETS,
108
+ metrics
109
+ })
95
110
 
96
111
  for (const [key, value] of Object.entries(resolvers)) {
97
112
  Multiaddr.resolvers.set(key, value)
@@ -128,6 +143,12 @@ class Dialer {
128
143
  * @returns {Promise<Connection>}
129
144
  */
130
145
  async connectToPeer (peer, options = {}) {
146
+ const { id } = getPeer(peer)
147
+
148
+ if (await this.connectionGater.denyDialPeer(id)) {
149
+ throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
150
+ }
151
+
131
152
  const dialTarget = await this._createCancellableDialTarget(peer)
132
153
 
133
154
  if (!dialTarget.addrs.length) {
@@ -192,10 +213,16 @@ class Dialer {
192
213
  const { id, multiaddrs } = getPeer(peer)
193
214
 
194
215
  if (multiaddrs) {
195
- this.peerStore.addressBook.add(id, multiaddrs)
216
+ await this.peerStore.addressBook.add(id, multiaddrs)
196
217
  }
197
218
 
198
- let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || []
219
+ let knownAddrs = await pipe(
220
+ await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter),
221
+ (source) => filter(source, async (multiaddr) => {
222
+ return !(await this.connectionGater.denyDialMultiaddr(id, multiaddr))
223
+ }),
224
+ (source) => all(source)
225
+ )
199
226
 
200
227
  // If received a multiaddr to dial, it should be the first to use
201
228
  // But, if we know other multiaddrs for the peer, we should try them too.
@@ -215,7 +242,7 @@ class Dialer {
215
242
  const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a))
216
243
 
217
244
  if (supportedAddrs.length > this.maxAddrsToDial) {
218
- this.peerStore.delete(id)
245
+ await this.peerStore.delete(id)
219
246
  throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
220
247
  }
221
248
 
@@ -259,7 +286,10 @@ class Dialer {
259
286
 
260
287
  // this signal will potentially be used while dialing lots of
261
288
  // peers so prevent MaxListenersExceededWarning appearing in the console
262
- setMaxListeners && setMaxListeners(Infinity, signal)
289
+ try {
290
+ // fails on node < 15.4
291
+ setMaxListeners && setMaxListeners(Infinity, signal)
292
+ } catch {}
263
293
 
264
294
  const pendingDial = {
265
295
  dialRequest,
package/src/errors.js CHANGED
@@ -12,6 +12,8 @@ exports.codes = {
12
12
  PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
13
13
  DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
14
14
  CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED',
15
+ ERR_PEER_DIAL_INTERCEPTED: 'ERR_PEER_DIAL_INTERCEPTED',
16
+ ERR_CONNECTION_INTERCEPTED: 'ERR_CONNECTION_INTERCEPTED',
15
17
  ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM',
16
18
  ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
17
19
  ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
@@ -0,0 +1,36 @@
1
+ libp2p-fetch JavaScript Implementation
2
+ =====================================
3
+
4
+ > Libp2p fetch protocol JavaScript implementation
5
+
6
+ ## Overview
7
+
8
+ An implementation of the Fetch protocol as described here: https://github.com/libp2p/specs/tree/master/fetch
9
+
10
+ The fetch protocol is a simple protocol for requesting a value corresponding to a key from a peer.
11
+
12
+ ## Usage
13
+
14
+ ```javascript
15
+ const Libp2p = require('libp2p')
16
+
17
+ /**
18
+ * Given a key (as a string) returns a value (as a Uint8Array), or null if the key isn't found.
19
+ * All keys must be prefixed my the same prefix, which will be used to find the appropriate key
20
+ * lookup function.
21
+ * @param key - a string
22
+ * @returns value - a Uint8Array value that corresponds to the given key, or null if the key doesn't
23
+ * have a corresponding value.
24
+ */
25
+ async function my_subsystem_key_lookup(key) {
26
+ // app specific callback to lookup key-value pairs.
27
+ }
28
+
29
+ // Enable this peer to respond to fetch requests for keys that begin with '/my_subsystem_key_prefix/'
30
+ const libp2p = Libp2p.create(...)
31
+ libp2p.fetchService.registerLookupFunction('/my_subsystem_key_prefix/', my_subsystem_key_lookup)
32
+
33
+ const key = '/my_subsystem_key_prefix/{...}'
34
+ const peerDst = PeerId.parse('Qmfoo...') // or Multiaddr instance
35
+ const value = await libp2p.fetch(peerDst, key)
36
+ ```
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ // https://github.com/libp2p/specs/tree/master/fetch#wire-protocol
5
+ PROTOCOL: '/libp2p/fetch/0.0.1'
6
+ }
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+
3
+ const debug = require('debug')
4
+ const log = Object.assign(debug('libp2p:fetch'), {
5
+ error: debug('libp2p:fetch:err')
6
+ })
7
+ const errCode = require('err-code')
8
+ const { codes } = require('../errors')
9
+ const lp = require('it-length-prefixed')
10
+ const { FetchRequest, FetchResponse } = require('./proto')
11
+ // @ts-ignore it-handshake does not export types
12
+ const handshake = require('it-handshake')
13
+ const { PROTOCOL } = require('./constants')
14
+
15
+ /**
16
+ * @typedef {import('../')} Libp2p
17
+ * @typedef {import('multiaddr').Multiaddr} Multiaddr
18
+ * @typedef {import('peer-id')} PeerId
19
+ * @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
20
+ * @typedef {(key: string) => Promise<Uint8Array | null>} LookupFunction
21
+ */
22
+
23
+ /**
24
+ * A simple libp2p protocol for requesting a value corresponding to a key from a peer.
25
+ * Developers can register one or more lookup function for retrieving the value corresponding to
26
+ * a given key. Each lookup function must act on a distinct part of the overall key space, defined
27
+ * by a fixed prefix that all keys that should be routed to that lookup function will start with.
28
+ */
29
+ class FetchProtocol {
30
+ /**
31
+ * @param {Libp2p} libp2p
32
+ */
33
+ constructor (libp2p) {
34
+ this._lookupFunctions = new Map() // Maps key prefix to value lookup function
35
+ this._libp2p = libp2p
36
+ this.handleMessage = this.handleMessage.bind(this)
37
+ }
38
+
39
+ /**
40
+ * Sends a request to fetch the value associated with the given key from the given peer.
41
+ *
42
+ * @param {PeerId|Multiaddr} peer
43
+ * @param {string} key
44
+ * @returns {Promise<Uint8Array | null>}
45
+ */
46
+ async fetch (peer, key) {
47
+ // @ts-ignore multiaddr might not have toB58String
48
+ log('dialing %s to %s', this._protocol, peer.toB58String ? peer.toB58String() : peer)
49
+
50
+ const connection = await this._libp2p.dial(peer)
51
+ const { stream } = await connection.newStream(FetchProtocol.PROTOCOL)
52
+ const shake = handshake(stream)
53
+
54
+ // send message
55
+ const request = new FetchRequest({ identifier: key })
56
+ shake.write(lp.encode.single(FetchRequest.encode(request).finish()))
57
+
58
+ // read response
59
+ const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
60
+ switch (response.status) {
61
+ case (FetchResponse.StatusCode.OK): {
62
+ return response.data
63
+ }
64
+ case (FetchResponse.StatusCode.NOT_FOUND): {
65
+ return null
66
+ }
67
+ case (FetchResponse.StatusCode.ERROR): {
68
+ const errmsg = (new TextDecoder()).decode(response.data)
69
+ throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS)
70
+ }
71
+ default: {
72
+ throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE)
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Invoked when a fetch request is received. Reads the request message off the given stream and
79
+ * responds based on looking up the key in the request via the lookup callback that corresponds
80
+ * to the key's prefix.
81
+ *
82
+ * @param {object} options
83
+ * @param {MuxedStream} options.stream
84
+ * @param {string} options.protocol
85
+ */
86
+ async handleMessage (options) {
87
+ const { stream } = options
88
+ const shake = handshake(stream)
89
+ const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
90
+
91
+ let response
92
+ const lookup = this._getLookupFunction(request.identifier)
93
+ if (lookup) {
94
+ const data = await lookup(request.identifier)
95
+ if (data) {
96
+ response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data })
97
+ } else {
98
+ response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND })
99
+ }
100
+ } else {
101
+ const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier)
102
+ response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg })
103
+ }
104
+
105
+ shake.write(lp.encode.single(FetchResponse.encode(response).finish()))
106
+ }
107
+
108
+ /**
109
+ * Given a key, finds the appropriate function for looking up its corresponding value, based on
110
+ * the key's prefix.
111
+ *
112
+ * @param {string} key
113
+ */
114
+ _getLookupFunction (key) {
115
+ for (const prefix of this._lookupFunctions.keys()) {
116
+ if (key.startsWith(prefix)) {
117
+ return this._lookupFunctions.get(prefix)
118
+ }
119
+ }
120
+ return null
121
+ }
122
+
123
+ /**
124
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
125
+ * share the same prefix.
126
+ *
127
+ * @param {string} prefix
128
+ * @param {LookupFunction} lookup
129
+ */
130
+ registerLookupFunction (prefix, lookup) {
131
+ if (this._lookupFunctions.has(prefix)) {
132
+ throw errCode(new Error("Fetch protocol handler for key prefix '" + prefix + "' already registered"), codes.ERR_KEY_ALREADY_EXISTS)
133
+ }
134
+ this._lookupFunctions.set(prefix, lookup)
135
+ }
136
+
137
+ /**
138
+ * Registers a new lookup callback that can map keys to values, for a given set of keys that
139
+ * share the same prefix.
140
+ *
141
+ * @param {string} prefix
142
+ * @param {LookupFunction} [lookup]
143
+ */
144
+ unregisterLookupFunction (prefix, lookup) {
145
+ if (lookup != null) {
146
+ const existingLookup = this._lookupFunctions.get(prefix)
147
+
148
+ if (existingLookup !== lookup) {
149
+ return
150
+ }
151
+ }
152
+
153
+ this._lookupFunctions.delete(prefix)
154
+ }
155
+ }
156
+
157
+ FetchProtocol.PROTOCOL = PROTOCOL
158
+
159
+ exports = module.exports = FetchProtocol
@@ -0,0 +1,134 @@
1
+ import * as $protobuf from "protobufjs";
2
+ /** Properties of a FetchRequest. */
3
+ export interface IFetchRequest {
4
+
5
+ /** FetchRequest identifier */
6
+ identifier?: (string|null);
7
+ }
8
+
9
+ /** Represents a FetchRequest. */
10
+ export class FetchRequest implements IFetchRequest {
11
+
12
+ /**
13
+ * Constructs a new FetchRequest.
14
+ * @param [p] Properties to set
15
+ */
16
+ constructor(p?: IFetchRequest);
17
+
18
+ /** FetchRequest identifier. */
19
+ public identifier: string;
20
+
21
+ /**
22
+ * Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
23
+ * @param m FetchRequest message or plain object to encode
24
+ * @param [w] Writer to encode to
25
+ * @returns Writer
26
+ */
27
+ public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer;
28
+
29
+ /**
30
+ * Decodes a FetchRequest message from the specified reader or buffer.
31
+ * @param r Reader or buffer to decode from
32
+ * @param [l] Message length if known beforehand
33
+ * @returns FetchRequest
34
+ * @throws {Error} If the payload is not a reader or valid buffer
35
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
36
+ */
37
+ public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchRequest;
38
+
39
+ /**
40
+ * Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
41
+ * @param d Plain object
42
+ * @returns FetchRequest
43
+ */
44
+ public static fromObject(d: { [k: string]: any }): FetchRequest;
45
+
46
+ /**
47
+ * Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
48
+ * @param m FetchRequest
49
+ * @param [o] Conversion options
50
+ * @returns Plain object
51
+ */
52
+ public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any };
53
+
54
+ /**
55
+ * Converts this FetchRequest to JSON.
56
+ * @returns JSON object
57
+ */
58
+ public toJSON(): { [k: string]: any };
59
+ }
60
+
61
+ /** Properties of a FetchResponse. */
62
+ export interface IFetchResponse {
63
+
64
+ /** FetchResponse status */
65
+ status?: (FetchResponse.StatusCode|null);
66
+
67
+ /** FetchResponse data */
68
+ data?: (Uint8Array|null);
69
+ }
70
+
71
+ /** Represents a FetchResponse. */
72
+ export class FetchResponse implements IFetchResponse {
73
+
74
+ /**
75
+ * Constructs a new FetchResponse.
76
+ * @param [p] Properties to set
77
+ */
78
+ constructor(p?: IFetchResponse);
79
+
80
+ /** FetchResponse status. */
81
+ public status: FetchResponse.StatusCode;
82
+
83
+ /** FetchResponse data. */
84
+ public data: Uint8Array;
85
+
86
+ /**
87
+ * Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
88
+ * @param m FetchResponse message or plain object to encode
89
+ * @param [w] Writer to encode to
90
+ * @returns Writer
91
+ */
92
+ public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer;
93
+
94
+ /**
95
+ * Decodes a FetchResponse message from the specified reader or buffer.
96
+ * @param r Reader or buffer to decode from
97
+ * @param [l] Message length if known beforehand
98
+ * @returns FetchResponse
99
+ * @throws {Error} If the payload is not a reader or valid buffer
100
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
101
+ */
102
+ public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchResponse;
103
+
104
+ /**
105
+ * Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
106
+ * @param d Plain object
107
+ * @returns FetchResponse
108
+ */
109
+ public static fromObject(d: { [k: string]: any }): FetchResponse;
110
+
111
+ /**
112
+ * Creates a plain object from a FetchResponse message. Also converts values to other types if specified.
113
+ * @param m FetchResponse
114
+ * @param [o] Conversion options
115
+ * @returns Plain object
116
+ */
117
+ public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any };
118
+
119
+ /**
120
+ * Converts this FetchResponse to JSON.
121
+ * @returns JSON object
122
+ */
123
+ public toJSON(): { [k: string]: any };
124
+ }
125
+
126
+ export namespace FetchResponse {
127
+
128
+ /** StatusCode enum. */
129
+ enum StatusCode {
130
+ OK = 0,
131
+ NOT_FOUND = 1,
132
+ ERROR = 2
133
+ }
134
+ }