helia-coord 1.1.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.
Files changed (57) hide show
  1. package/.on-save.json +8 -0
  2. package/PEDIGREE.md +3 -0
  3. package/README.md +3 -0
  4. package/config/bootstrap-circuit-relays.js +48 -0
  5. package/examples/start-external.js +56 -0
  6. package/index.js +129 -0
  7. package/lib/adapters/bch-adapter.js +94 -0
  8. package/lib/adapters/encryption-adapter.js +123 -0
  9. package/lib/adapters/gist.js +42 -0
  10. package/lib/adapters/index.js +66 -0
  11. package/lib/adapters/ipfs-adapter.js +263 -0
  12. package/lib/adapters/logs-adapter.js +44 -0
  13. package/lib/adapters/pubsub-adapter/README.md +86 -0
  14. package/lib/adapters/pubsub-adapter/about-adapter.js +117 -0
  15. package/lib/adapters/pubsub-adapter/index.js +275 -0
  16. package/lib/adapters/pubsub-adapter/messaging.js +389 -0
  17. package/lib/adapters/pubsub-adapter/msg-router.js +58 -0
  18. package/lib/adapters/pubsub-adapter/resend-msg.js +58 -0
  19. package/lib/controllers/index.js +24 -0
  20. package/lib/controllers/timer-controller.js +417 -0
  21. package/lib/entities/this-node-entity.js +102 -0
  22. package/lib/use-cases/index.js +36 -0
  23. package/lib/use-cases/peer-use-cases.js +146 -0
  24. package/lib/use-cases/pubsub-use-cases.js +56 -0
  25. package/lib/use-cases/relay-use-cases.js +479 -0
  26. package/lib/use-cases/schema.js +158 -0
  27. package/lib/use-cases/this-node-use-cases.js +443 -0
  28. package/lib/util/utils.js +12 -0
  29. package/package.json +52 -0
  30. package/test/mocks/adapter-mock.js +119 -0
  31. package/test/mocks/circuit-relay-mocks.js +67 -0
  32. package/test/mocks/ipfs-mock.js +46 -0
  33. package/test/mocks/peers-mock.js +75 -0
  34. package/test/mocks/pubsub-mocks.js +37 -0
  35. package/test/mocks/thisnode-mocks.js +82 -0
  36. package/test/mocks/use-case-mocks.js +24 -0
  37. package/test/unit/adapters/bch-adapter-unit.js +96 -0
  38. package/test/unit/adapters/encryption-adapter-unit.js +129 -0
  39. package/test/unit/adapters/gist.unit.adapters.js +58 -0
  40. package/test/unit/adapters/index-adapters-unit.js +79 -0
  41. package/test/unit/adapters/ipfs-adapter-unit.js +215 -0
  42. package/test/unit/adapters/logs-adapter-unit.js +55 -0
  43. package/test/unit/adapters/pubsub/about-adapter-unit.js +129 -0
  44. package/test/unit/adapters/pubsub/messaging-adapter-unit.js +576 -0
  45. package/test/unit/adapters/pubsub/pubsub-adapter-unit.js +367 -0
  46. package/test/unit/adapters/pubsub/resend-msg-adapter-unit.js +58 -0
  47. package/test/unit/controllers/controllers-index-unit.js +30 -0
  48. package/test/unit/controllers/timer-controller-unit.js +261 -0
  49. package/test/unit/entities/this-node.unit.entity.js +157 -0
  50. package/test/unit/index-unit.js +160 -0
  51. package/test/unit/use-cases/peer.unit.use-cases.js +186 -0
  52. package/test/unit/use-cases/pubsub.unit.use-cases.js +114 -0
  53. package/test/unit/use-cases/relay-use-cases-unit.js +658 -0
  54. package/test/unit/use-cases/schema-use-case-unit.js +101 -0
  55. package/test/unit/use-cases/this-node-use-cases-unit.js +427 -0
  56. package/test/unit/use-cases/use-cases-index-unit.js +47 -0
  57. package/test/unit/util/utils-unit.js +31 -0
@@ -0,0 +1,146 @@
1
+ /*
2
+ Use cases for interacting with subnet peer nodes.
3
+ */
4
+
5
+ class PeerUseCases {
6
+ constructor (localConfig = {}) {
7
+ // Dependency Injection.
8
+ this.adapters = localConfig.adapters
9
+ if (!this.adapters) {
10
+ throw new Error(
11
+ 'Must inject instance of adapters when instantiating Peer Use Cases library.'
12
+ )
13
+ }
14
+ }
15
+
16
+ // Connect to a peer through available circuit relays. This ensures a short
17
+ // path between peers, *before* broadcasting the OrbitDB message to them.
18
+ // This method is primarily used by sendPrivateMessage() to allow for fast-
19
+ // startup connection and communication with peers.
20
+ async connectToPeer (peerId, thisNode) {
21
+ try {
22
+ // console.log(`connectToPeer() called on ${peerId}`)
23
+
24
+ const relays = thisNode.relayData
25
+
26
+ // Get connected peers
27
+ const connectedPeers = await this.adapters.ipfs.getPeers()
28
+
29
+ // Check if target peer is currently conected to the node.
30
+ const connectedPeer = connectedPeers.filter(
31
+ peerObj => peerObj.peer === peerId
32
+ )
33
+
34
+ // If this node is already connected to the peer, then return.
35
+ // We do not need to do anything.
36
+ if (connectedPeer.length) {
37
+ return true
38
+ }
39
+
40
+ // Sort the Circuit Relays by the average of the aboutLatency
41
+ // array. Connect to peers through the Relays with the lowest latencies
42
+ // first.
43
+ const sortedRelays = thisNode.useCases.relays.sortRelays(relays)
44
+ // console.log(`sortedRelays: ${JSON.stringify(sortedRelays, null, 2)}`)
45
+
46
+ // Loop through each known circuit relay and attempt to connect to the
47
+ // peer through a relay.
48
+ for (let i = 0; i < sortedRelays.length; i++) {
49
+ const thisRelay = sortedRelays[i]
50
+ // console.log(`thisRelay: ${JSON.stringify(thisRelay, null, 2)}`)
51
+
52
+ // Generate a multiaddr for connecting to the peer through a circuit relay.
53
+ const multiaddr = `${thisRelay.multiaddr}/p2p-circuit/p2p/${peerId}`
54
+ // console.log(`multiaddr: ${multiaddr}`)
55
+
56
+ // Skip the relay if this node is not connected to it.
57
+ if (thisRelay.connected) {
58
+ // Attempt to connect to the node through a circuit relay.
59
+ const connected = await this.adapters.ipfs.connectToPeer(multiaddr)
60
+
61
+ // If the connection was successful, break out of the relay loop.
62
+ // Otherwise try to connect through the next relay.
63
+ if (connected) {
64
+ // Exit once we've made a successful connection.
65
+ return true
66
+ }
67
+ }
68
+ }
69
+
70
+ // Return false to indicate connection was unsuccessful.
71
+ return false
72
+ } catch (err) {
73
+ console.error('Error in peer-use-cases.js/connectToPeer()')
74
+ throw err
75
+ }
76
+ }
77
+
78
+ // Publish a string of text to another peers OrbitDB recieve database.
79
+ // orbitdbId input is optional.
80
+ async sendPrivateMessage (peerId, str, thisNode) {
81
+ try {
82
+ // console.log('sendPrivateMessage() peerId: ', peerId)
83
+ // console.log('\nsendPrivateMessage() str: ', str)
84
+
85
+ // const peer = this.peers.state.peers[peerId]
86
+ // console.log('thisNode.peerData: ', thisNode.peerData)
87
+ const peerData = thisNode.peerData.filter(x => x.from === peerId)
88
+ // console.log(
89
+ // `sendPrivateMessage peerData: ${JSON.stringify(peerData, null, 2)}`
90
+ // )
91
+
92
+ // Throw an error if the peer matching the peerId is not found.
93
+ if (peerData.length === 0) {
94
+ throw new Error(`Data for peer ${peerId} not found.`)
95
+ }
96
+
97
+ // Encrypt the string with the peers public key.
98
+ const encryptedStr = await this.adapters.encryption.encryptMsg(
99
+ peerData[0],
100
+ str
101
+ )
102
+
103
+ // Publish the message to the peers pubsub channel.
104
+ await this.adapters.pubsub.messaging.sendMsg(
105
+ peerId,
106
+ encryptedStr,
107
+ thisNode
108
+ )
109
+ // console.log('--->Successfully published to pubsub channel<---')
110
+
111
+ // const now = new Date()
112
+ //
113
+ // const peerDb = thisNode.orbitData.filter(x => x.ipfsId === peerId)
114
+ // // console.log('peerDb: ', peerDb)
115
+ //
116
+ // // Throw an error if peer database was not found.
117
+ // if (peerDb.length === 0) {
118
+ // throw new Error(`OrbitDB for peer ${peerId} not found.`)
119
+ // }
120
+ //
121
+ // // Connect to peer through Circuit Relay, using connectToPeer()
122
+ // // Note: isConnected will resolve to false if this node can not connect to
123
+ // // the peer. It will resolve to true if it successfully connected.
124
+ // await this.connectToPeer(peerId, thisNode)
125
+ //
126
+ // const db = peerDb[0].db
127
+ //
128
+ // const dbObj = {
129
+ // from: thisNode.ipfsId,
130
+ // data: encryptedStr,
131
+ // timestamp: now.toISOString()
132
+ // }
133
+ // // console.log(`dbObj: ${JSON.stringify(dbObj, null, 2)}`)
134
+ //
135
+ // // Add the encrypted message to the peers OrbitDB.
136
+ // await db.add(dbObj)
137
+
138
+ return true
139
+ } catch (err) {
140
+ console.error('Error in peer-use-cases.js/sendPrivateMessage(): ', err)
141
+ throw err
142
+ }
143
+ }
144
+ }
145
+
146
+ export default PeerUseCases
@@ -0,0 +1,56 @@
1
+ /*
2
+ A Use Case library for interacting with the Pubsub Entity.
3
+ */
4
+
5
+ const DEFAULT_COORDINATION_ROOM = 'psf-ipfs-coordination-002'
6
+ const BCH_COINJOIN_ROOM = 'bch-coinjoin-001'
7
+
8
+ class PubsubUseCase {
9
+ constructor (localConfig = {}) {
10
+ // Dependency Injection.
11
+ this.adapters = localConfig.adapters
12
+ if (!this.adapters) {
13
+ throw new Error(
14
+ 'Must inject instance of adapters when instantiating Pubsub Use Cases library.'
15
+ )
16
+ }
17
+ this.thisNodeUseCases = localConfig.thisNodeUseCases
18
+ if (!this.thisNodeUseCases) {
19
+ throw new Error(
20
+ 'thisNode use cases required when instantiating Pubsub Use Cases library.'
21
+ )
22
+ }
23
+
24
+ // Allow the app to override the default CoinJoin pubsub handler.
25
+ this.coinjoinPubsubHandler = () => true
26
+ if (localConfig.coinjoinPubsubHandler) this.coinjoinPubsubHandler = localConfig.coinjoinPubsubHandler
27
+ }
28
+
29
+ // Connect to the default pubsub rooms.
30
+ async initializePubsub (thisNode) {
31
+ try {
32
+ // Subscribe to the coordination channel, where new peers announce themselves
33
+ // to the network.
34
+ await this.adapters.pubsub.subscribeToPubsubChannel(
35
+ DEFAULT_COORDINATION_ROOM,
36
+ // this.adapters.peers.addPeer
37
+ this.thisNodeUseCases.addSubnetPeer,
38
+ thisNode
39
+ )
40
+
41
+ // Subscribe to the BCH CoinJoin coordination channel. This code is here
42
+ // so that Circuit Relays automatically subscribe to the channel and
43
+ // relay the messages.
44
+ await this.adapters.pubsub.subscribeToPubsubChannel(
45
+ BCH_COINJOIN_ROOM,
46
+ this.coinjoinPubsubHandler,
47
+ thisNode
48
+ )
49
+ } catch (err) {
50
+ console.error('Error in pubsub-use-cases.js/initializePubsub()')
51
+ throw err
52
+ }
53
+ }
54
+ }
55
+
56
+ export default PubsubUseCase
@@ -0,0 +1,479 @@
1
+ /*
2
+ A Use Case library for working with Circuit Relays.
3
+
4
+ TODO: Build these methods:
5
+ - pruneConnections() - Disconnect dead or misbehaving nodes.
6
+ - addRelay() - Add a new relay to the state.
7
+ */
8
+
9
+ import bootstrapCircuitRelays from '../../config/bootstrap-circuit-relays.js'
10
+ // const RelayEntity = require('../entities/relay-entity')
11
+
12
+ class RelayUseCases {
13
+ constructor (localConfig = {}) {
14
+ // Dependency Injection.
15
+ this.adapters = localConfig.adapters
16
+ if (!this.adapters) {
17
+ throw new Error(
18
+ 'Must inject instance of adapters when instantiating Relay Use Cases library.'
19
+ )
20
+ }
21
+ // this.controllers = localConfig.controllers
22
+ // if (!this.controllers) {
23
+ // throw new Error(
24
+ // 'Must inject instance of controllers when instantiating Relay Use Cases library.'
25
+ // )
26
+ // }
27
+ }
28
+
29
+ // Connect to the pre-programmed circuit relays for the first time at startup.
30
+ async initializeRelays (thisNode) {
31
+ try {
32
+ // Set the initial CR bootstrap nodes based on the type of IPFS node.
33
+ let bootstrapRelays = []
34
+ if (thisNode.type === 'browser') {
35
+ bootstrapRelays = bootstrapCircuitRelays.browser
36
+ } else {
37
+ bootstrapRelays = bootstrapCircuitRelays.node
38
+ }
39
+
40
+ // Loop through each bootstrap Relay.
41
+ for (let i = 0; i < bootstrapRelays.length; i++) {
42
+ const thisRelay = bootstrapRelays[i]
43
+
44
+ // Attempt to connect to the Circuit Relay peer.
45
+ const connectionStatus = await this.adapters.ipfs.connectToPeer(
46
+ thisRelay.multiaddr
47
+ )
48
+
49
+ const now = new Date()
50
+
51
+ const newRelayObj = {
52
+ multiaddr: thisRelay.multiaddr,
53
+ connected: connectionStatus,
54
+ updatedAt: now,
55
+ ipfsId: thisRelay.ipfsId,
56
+ isBootstrap: true,
57
+ metrics: {
58
+ aboutLatency: []
59
+ }
60
+ }
61
+
62
+ // Record relay information in thisNode entity.
63
+ thisNode.relayData.push(newRelayObj)
64
+ }
65
+
66
+ return true
67
+ } catch (err) {
68
+ console.error('Error in relay-use-case.js/initializeRelays()')
69
+ throw err
70
+ }
71
+ }
72
+
73
+ // Download a list of Circuit Relays from a GitHub Gist, and try to connect
74
+ // to all of the ones in the list.
75
+ async getCRGist (thisNode) {
76
+ try {
77
+ const gistCRs = await this.adapters.gist.getCRList()
78
+
79
+ let gistRelays
80
+ if (thisNode.type === 'browser') {
81
+ gistRelays = gistCRs.browser
82
+ } else {
83
+ gistRelays = gistCRs.node
84
+ }
85
+
86
+ console.log(`Relays from the gist: ${JSON.stringify(gistRelays, null, 2)}`)
87
+
88
+ for (let i = 0; i < gistRelays.length; i++) {
89
+ const thisRelay = gistRelays[i]
90
+
91
+ // If this relay is already in the list of circuit relays, then skip.
92
+ const alreadyExists = thisNode.relayData.filter(
93
+ x => x.ipfsId === thisRelay.ipfsId
94
+ )
95
+ if (alreadyExists.length) continue
96
+
97
+ // Attempt to connect to the Circuit Relay peer.
98
+ console.log(`getCRGist() Connecting to Circuit Relay ${thisRelay.multiaddr}`)
99
+ const connectionStatus = await this.adapters.ipfs.connectToPeer(
100
+ thisRelay.multiaddr
101
+ )
102
+
103
+ const now = new Date()
104
+
105
+ const newRelayObj = {
106
+ multiaddr: thisRelay.multiaddr,
107
+ connected: connectionStatus,
108
+ updatedAt: now,
109
+ ipfsId: thisRelay.ipfsId,
110
+ isBootstrap: false,
111
+ metrics: {
112
+ aboutLatency: []
113
+ }
114
+ }
115
+
116
+ // Record relay information in thisNode entity.
117
+ thisNode.relayData.push(newRelayObj)
118
+ }
119
+
120
+ // Remove duplicate Circuit Relays.
121
+ await this.removeDuplicates(thisNode)
122
+
123
+ return true
124
+ } catch (err) {
125
+ console.error('Error in relay-use-case.js/getCRGist()')
126
+ throw err
127
+ }
128
+ }
129
+
130
+ // Renew the connection to each circuit relay in the state.
131
+ async connectToCRs (thisNode) {
132
+ try {
133
+ let knownRelays = thisNode.relayData
134
+
135
+ // Sort the Relays by their latency. This way Relays with the lowest latency
136
+ // (relative to thisNode) are used first.
137
+ knownRelays = this.sortRelays(knownRelays)
138
+ // console.log('knownRelays: ', knownRelays)
139
+
140
+ for (let i = 0; i < knownRelays.length; i++) {
141
+ const thisRelay = knownRelays[i]
142
+ // console.log('thisRelay: ', thisRelay)
143
+
144
+ // Get connected peers
145
+ const connectedPeers = await this.adapters.ipfs.getPeers()
146
+ // console.log('connectedPeers: ', connectedPeers)
147
+
148
+ // Check if target circuit-relay is currently conected to the node.
149
+ const connectedPeer = connectedPeers.filter(peerObj => thisRelay.multiaddr.match(peerObj.peer)
150
+ )
151
+ // console.log('connectedPeer: ', connectedPeer)
152
+
153
+ // Update the connection state.
154
+
155
+ if (connectedPeer.length) {
156
+ // Already connected.
157
+ thisRelay.connected = true
158
+ console.log(`Already connected to Circuit Relay ${thisRelay.multiaddr}`)
159
+ } else {
160
+ // If this node is not connected, try to connect again.
161
+ console.log(`Connecting to Circuit Relay ${thisRelay.multiaddr}`)
162
+ thisRelay.connected = await this.adapters.ipfs.connectToPeer(
163
+ thisRelay.multiaddr
164
+ )
165
+ console.log(`thisRelay.connected: ${thisRelay.connected}`)
166
+
167
+ if (thisRelay.connected) {
168
+ this.adapters.log.statusLog(
169
+ 0,
170
+ `Connected to Circuit Relay peer ${thisRelay.ipfsId}`
171
+ )
172
+ console.log(`connectToCRs() Connected to Circuit Relay peer ${thisRelay.ipfsId}`)
173
+ }
174
+ }
175
+ // console.log('thisRelay.connected: ', thisRelay.connected)
176
+
177
+ // Update the timestamp.
178
+ const now = new Date()
179
+ thisRelay.updatedAt = now
180
+
181
+ // Update the state tracked by This Node.
182
+ thisNode.relayData[i] = thisRelay
183
+ }
184
+
185
+ // const now = new Date()
186
+ // this.adapters.log.statusLog(
187
+ // `status: ${now.toLocaleString()}: Renewed connections to all known Circuit Relay nodes.`
188
+ // )
189
+ } catch (err) {
190
+ console.log('Error in connectToCRs()')
191
+ return false
192
+ }
193
+ }
194
+
195
+ // Sort the relay data relative to the measured latency metrics.
196
+ sortRelays (relayData) {
197
+ try {
198
+ // Loop through each element and add a latency score.
199
+ for (let i = 0; i < relayData.length; i++) {
200
+ const thisRelay = relayData[i]
201
+
202
+ // Ensure an initial default value is assigned.
203
+ thisRelay.latencyScore = 10000
204
+
205
+ // Assign bootstrap nodes the maximum latency.
206
+ if (thisRelay.isBootstrap) {
207
+ thisRelay.latencyScore = 10000
208
+ } else {
209
+ // Average the latencies measured for this Relay.
210
+ thisRelay.latencyScore = Math.floor(
211
+ this._average(thisRelay.metrics.aboutLatency)
212
+ )
213
+
214
+ // Handle corner-case of empty arrays.
215
+ if (isNaN(thisRelay.latencyScore)) thisRelay.latencyScore = 10000
216
+ }
217
+
218
+ // console.log('thisRelay: ', thisRelay)
219
+ }
220
+
221
+ // Sort the array by the latency score.
222
+ // https://flaviocopes.com/how-to-sort-array-of-objects-by-property-javascript/
223
+ const sortedList = relayData.sort((a, b) => a.latencyScore > b.latencyScore ? 1 : -1
224
+ )
225
+ // console.log('Relay sortedList: ', sortedList)
226
+
227
+ return sortedList
228
+ } catch (err) {
229
+ console.error('Error in sortRelays()')
230
+ throw err
231
+ }
232
+ }
233
+
234
+ // Average together the elements of an array.
235
+ _average (arr) {
236
+ return arr.reduce((p, c) => p + c, 0) / arr.length
237
+ }
238
+
239
+ // If a subnet Peer has the isCircuitRelay flag set, try to connect to them
240
+ // directly. If the connection succeeds, add them to the list of Circuit Relays.
241
+ // Triggered when a new subnet peer is found that has their Relay flag set.
242
+ async addRelay (ipfsId, thisNode) {
243
+ try {
244
+ const now = new Date()
245
+ // console.log(`debug: addRelay() called at ${now.toLocaleString()}`)
246
+
247
+ // Check to see if peer is already in the list of Circuit Relays.
248
+ const alreadyInList = thisNode.relayData.filter(x => x.ipfsId.includes(ipfsId)
249
+ )
250
+ // Exit if this peer is already in the list of Relays.
251
+ if (alreadyInList.length) return true
252
+
253
+ // this.adapters.log.statusLog(1, 'New Circuit Relay Peer Found!')
254
+
255
+ // Get the peer data.
256
+ let peerData = thisNode.peerData.filter(x => x.data.ipfsId === ipfsId)
257
+ peerData = peerData[0]
258
+ // console.log('peerData: ', peerData)
259
+
260
+ // Exit if this peer is not a circuit relay.
261
+ // CT Added 9/4/21.
262
+ // Added because testing was showing that some non-relay peers where getting
263
+ // pings with about metrics.
264
+ const isCircuitRelay = peerData.data.isCircuitRelay
265
+ if (!isCircuitRelay) return false // Return true?
266
+
267
+ this.adapters.log.statusLog(
268
+ 1,
269
+ `New Circuit Relay Peer Found: ${peerData.from}`
270
+ )
271
+
272
+ // Grab the optional connection information if the Circuit Relay provides
273
+ // it. If they do, construct a connection string and add it to the
274
+ // list of multiaddrs.
275
+ const circuitRelayInfo = peerData.data.circuitRelayInfo
276
+ if (circuitRelayInfo) {
277
+ if (circuitRelayInfo.ip4) {
278
+ const ip4TcpMultiaddr = `/ip4/${circuitRelayInfo.ip4}/tcp/${circuitRelayInfo.tcpPort}/p2p/${peerData.from}`
279
+ peerData.data.ipfsMultiaddrs.push(ip4TcpMultiaddr)
280
+ }
281
+ if (circuitRelayInfo.crDomain) {
282
+ const dnsMultiaddr = `/dns4/${circuitRelayInfo.crDomain}/tcp/443/wss/ipfs/${peerData.from}`
283
+ peerData.data.ipfsMultiaddrs.push(dnsMultiaddr)
284
+ }
285
+
286
+ // Debugging
287
+ // console.log(
288
+ // `peer multiaddrs: ${JSON.stringify(
289
+ // peerData.data.ipfsMultiaddrs,
290
+ // null,
291
+ // 2
292
+ // )}`
293
+ // )
294
+ }
295
+
296
+ let multiaddr = ''
297
+ let connectSuccess = false
298
+
299
+ // Try to connect to one of the multiaddrs.
300
+ for (let i = 0; i < peerData.data.ipfsMultiaddrs.length; i++) {
301
+ const thisAddr = peerData.data.ipfsMultiaddrs[i]
302
+ // console.log('thisAddr: ', thisAddr)
303
+
304
+ // Try to connect to the IPFS peer.
305
+ connectSuccess = await this.adapters.ipfs.connectToPeer(thisAddr)
306
+
307
+ // If connection was successful
308
+ if (connectSuccess) {
309
+ // Get a list of connect peers.
310
+ const peers = await this.adapters.ipfs.getPeers()
311
+ // console.log('addRelay() peers: ', peers)
312
+
313
+ // Retrieve the multiaddr that worked for connecting.
314
+ const thisRelay = peers.filter(x => x.peer.toString() === ipfsId)
315
+ // console.log('thisRelay: ', thisRelay)
316
+ multiaddr = thisRelay[0].addr.toString()
317
+ // console.log('multiaddr: ', multiaddr)
318
+
319
+ // If multiaddr does not contain ip4, ip6, or dns, then reject it,
320
+ // because the connection is happening through another Relay.
321
+ // If the multiaddr has 'p2p-circuit' in it, then it's also happening
322
+ // through a Relay and should be rejected.
323
+ const isIp4 = multiaddr.includes('ip4')
324
+ const isIp6 = multiaddr.includes('ip6')
325
+ const isDns = multiaddr.includes('dns')
326
+ const isBridged = multiaddr.includes('p2p-circuit')
327
+ if ((!isIp4 && !isIp6 && !isDns) || isBridged) {
328
+ this.adapters.log.statusLog(
329
+ 1,
330
+ `Rejecting Relay multiaddr ${multiaddr}`
331
+ )
332
+
333
+ // Disconnect from the peer, so that a direct connection can be
334
+ // attempted.
335
+ await this.adapters.ipfs.disconnectFromPeer(ipfsId)
336
+
337
+ connectSuccess = false
338
+ continue
339
+ }
340
+
341
+ break
342
+ } else {
343
+ this.adapters.log.statusLog(
344
+ 1,
345
+ `Connecting to potential relay did not succeed with multiaddr ${thisAddr}`
346
+ )
347
+ }
348
+ }
349
+
350
+ // If we could not successfully connect to the peer with any of the given
351
+ // multiaddrs, then do not add it to the list of Relays.
352
+ if (!connectSuccess) {
353
+ this.adapters.log.statusLog(
354
+ 1,
355
+ `: Could not add potential Circuit Relay: ${ipfsId}`
356
+ )
357
+ return false
358
+ }
359
+
360
+ // Add tail end of multiaddr if it's omitted. (this happens in go-ipfs v0.11.0)
361
+ if (!multiaddr.includes(ipfsId)) {
362
+ multiaddr = `${multiaddr}/p2p/${ipfsId}`
363
+ }
364
+
365
+ // On successful connection, add the Circuit Relay to the list.
366
+ // const now = new Date()
367
+ const newRelayObj = {
368
+ multiaddr,
369
+ connected: true,
370
+ updatedAt: now,
371
+ ipfsId,
372
+ isBootstrap: false,
373
+ metrics: {
374
+ aboutLatency: []
375
+ }
376
+ }
377
+ // console.log(`newRelayObj: ${JSON.stringify(newRelayObj, null, 2)}`)
378
+ thisNode.relayData.push(newRelayObj)
379
+
380
+ console.log(`New Circuit Relay added: ${multiaddr}`)
381
+
382
+ return true
383
+ } catch (err) {
384
+ console.error('Error in addRelay(): ', err)
385
+ // top-level function. Do not throw an error.
386
+ return false
387
+ }
388
+ }
389
+
390
+ // Called when periodically checking the Relay connection. Maintains network
391
+ // metrics about each Relay.
392
+ // Called by the manageCircuitRelays() Timer Controller.
393
+ async measureRelays (thisNode) {
394
+ try {
395
+ // console.log('Entering measureRelays()')
396
+ // console.log('thisNode.relayData: ', thisNode.relayData)
397
+ // console.log(
398
+ // `thisNode.peerData: ${JSON.stringify(thisNode.peerData, null, 2)}`
399
+ // )
400
+
401
+ // Loop through each Circuit Relay
402
+ for (let i = 0; i < thisNode.relayData.length; i++) {
403
+ const thisRelay = thisNode.relayData[i]
404
+
405
+ // If the relay is from the bootstrap list, skip it.
406
+ // if (thisRelay.isBootstrap) continue
407
+
408
+ // Get the peer data.
409
+ let peerData = thisNode.peerData.filter(x => {
410
+ // console.log(`x.data.ipfsId: ${x.data.ipfsId}`)
411
+ // console.log(`thisRelay.ipfsId: ${thisRelay.ipfsId}`)
412
+ return x.data.ipfsId === thisRelay.ipfsId
413
+ })
414
+ peerData = peerData[0]
415
+ // console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
416
+
417
+ // Skip if this Relay is not advertising as a relay.
418
+ if (!peerData || !peerData.data.isCircuitRelay) continue
419
+
420
+ // console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
421
+
422
+ // If this node is not connected to the Relay, give it the worst score.
423
+ if (!thisRelay.connected) {
424
+ thisRelay.metrics.aboutLatency.push(10000)
425
+ continue
426
+ }
427
+
428
+ // Poll the /about JSON endpoint, and track the time it takes to recieve
429
+ // a response.
430
+ const startTime = new Date()
431
+ const testResult = await this.adapters.pubsub.about.queryAbout(
432
+ thisRelay.ipfsId,
433
+ thisNode
434
+ )
435
+ // await this.adapters.bch.bchjs.Util.sleep(2000)
436
+ const endTime = new Date()
437
+
438
+ let diffTime = 10000
439
+ if (testResult) diffTime = endTime.getTime() - startTime.getTime()
440
+ // console.log(`Time difference: ${diffTime} mS`)
441
+
442
+ // Prune the metrics array if it's too big.
443
+ if (thisRelay.metrics.aboutLatency.length > 9) {
444
+ thisRelay.metrics.aboutLatency.shift()
445
+ }
446
+
447
+ // Save the new metric value.
448
+ thisRelay.metrics.aboutLatency.push(diffTime)
449
+ }
450
+ } catch (err) {
451
+ console.error('Error in measureRelays(): ', err)
452
+ throw err
453
+ }
454
+ }
455
+
456
+ // Remove any duplicate entries of Circuit Relays.
457
+ // This is middleware that edits the thisNode.relayData in place.
458
+ removeDuplicates (thisNode) {
459
+ try {
460
+ const startingRelays = thisNode.relayData
461
+ // console.log('thisNode.relayData: ', thisNode.relayData)
462
+
463
+ // https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
464
+ const endingRelays = startingRelays.filter(
465
+ (relay, index, self) => index === self.findIndex(t => t.ipfsId === relay.ipfsId)
466
+ )
467
+ // console.log('endingRelays: ', endingRelays)
468
+
469
+ thisNode.relayData = endingRelays
470
+
471
+ return true
472
+ } catch (err) {
473
+ console.error('Error in removeDuplicates()')
474
+ throw err
475
+ }
476
+ }
477
+ }
478
+
479
+ export default RelayUseCases