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,158 @@
1
+ /*
2
+ Schema templates for sending and recieving messages from other IPFS peers.
3
+ */
4
+
5
+ class Schema {
6
+ constructor (schemaConfig) {
7
+ this.ipfsId = schemaConfig.ipfsId ? schemaConfig.ipfsId : null
8
+
9
+ // Initialize the state with default values.
10
+ this.state = {
11
+ ipfsId: this.ipfsId,
12
+ name: schemaConfig.name ? schemaConfig.name : this.ipfsId,
13
+ type: schemaConfig.type ? schemaConfig.type : null,
14
+ ipfsMultiaddrs: schemaConfig.ipfsMultiaddrs
15
+ ? schemaConfig.ipfsMultiaddrs
16
+ : [],
17
+ isCircuitRelay: schemaConfig.isCircuitRelay
18
+ ? schemaConfig.isCircuitRelay
19
+ : false,
20
+ circuitRelayInfo: schemaConfig.circuitRelayInfo
21
+ ? schemaConfig.circuitRelayInfo
22
+ : {},
23
+ cashAddress: schemaConfig.cashAddress || '',
24
+ slpAddress: schemaConfig.slpAddress || '',
25
+ publicKey: schemaConfig.publicKey || '',
26
+ orbitdbId: schemaConfig.orbitdbId || '',
27
+
28
+ // Default API Info. This should be a link to the API documenation, passed
29
+ // in by the consumer of the ipfs-coord library.
30
+ apiInfo: schemaConfig.apiInfo ||
31
+ 'You should put an IPFS hash or web URL here to your documentation.'
32
+ }
33
+
34
+ // Default JSON-LD Schema
35
+ this.state.announceJsonLd = schemaConfig.announceJsonLd || {
36
+ '@context': 'https://schema.org/',
37
+ '@type': 'WebAPI',
38
+ name: this.state.name,
39
+ description: 'IPFS Coordination Library is used. This app has not been customized.',
40
+ documentation: 'https://www.npmjs.com/package/ipfs-coord',
41
+ provider: {
42
+ '@type': 'Organization',
43
+ name: 'Permissionless Software Foundation',
44
+ url: 'https://PSFoundation.cash'
45
+ }
46
+ }
47
+
48
+ this.state.announceJsonLd.identifier = this.ipfsId
49
+ }
50
+
51
+ // Returns a JSON object that represents an announement message.
52
+ announcement (announceObj) {
53
+ const now = new Date()
54
+
55
+ // Update the orbitdb ID in the state, if it's changed.
56
+ if (
57
+ announceObj &&
58
+ announceObj.orbitdbId &&
59
+ announceObj.orbitdbId !== this.state.orbitdbId
60
+ ) {
61
+ this.state.orbitdbId = announceObj.orbitdbId
62
+ }
63
+
64
+ const retObj = {
65
+ apiName: 'ipfs-coord-announce',
66
+ apiVersion: '1.3.2',
67
+ apiInfo: this.state.apiInfo,
68
+
69
+ // Add a timestamp
70
+ broadcastedAt: now.toISOString(),
71
+
72
+ // IPFS specific information for this node.
73
+ ipfsId: this.state.ipfsId,
74
+ type: this.state.type,
75
+ ipfsMultiaddrs: this.state.ipfsMultiaddrs,
76
+ orbitdb: this.state.orbitdbId,
77
+
78
+ // The circuit relays preferred by this node.
79
+ circuitRelays: [],
80
+ isCircuitRelay: this.state.isCircuitRelay,
81
+ circuitRelayInfo: this.state.circuitRelayInfo,
82
+
83
+ // Array of objects, containing addresses for different blockchains.
84
+ cryptoAddresses: [
85
+ {
86
+ blockchain: 'BCH',
87
+ type: 'cashAddr',
88
+ address: this.state.cashAddress
89
+ },
90
+ {
91
+ blockchain: 'BCH',
92
+ type: 'slpAddr',
93
+ address: this.state.slpAddress
94
+ }
95
+ ],
96
+
97
+ // BCH public key, used for e2e encryption.
98
+ encryptPubKey: this.state.publicKey,
99
+
100
+ // Schema.org and JSON Linked Data
101
+ jsonLd: this.state.announceJsonLd
102
+ }
103
+
104
+ return retObj
105
+ }
106
+
107
+ // Returns a JSON object that represents a chat message.
108
+ // Inputs:
109
+ // - message - string text message
110
+ // - handle - the desired display name for the user
111
+ chat (msgObj) {
112
+ const { message, handle } = msgObj
113
+
114
+ const retObj = {
115
+ apiName: 'chat',
116
+ apiVersion: '1.3.2',
117
+ apiInfo: this.state.apiInfo,
118
+
119
+ // IPFS specific information for this node.
120
+ ipfsId: this.state.ipfsId,
121
+ type: this.state.type,
122
+ ipfsMultiaddrs: this.state.ipfsMultiaddrs,
123
+
124
+ // The circuit relays preferred by this node.
125
+ circuitRelays: [],
126
+
127
+ // Array of objects, containing addresses for different blockchains.
128
+ cryptoAddresses: [],
129
+
130
+ // BCH public key, used for e2e encryption.
131
+ encryptPubKey: '',
132
+
133
+ data: {
134
+ message,
135
+ handle
136
+ },
137
+
138
+ // Schema.org and JSON Linked Data
139
+ jsonLd: {
140
+ '@context': 'https://schema.org/',
141
+ '@type': 'CommentAction',
142
+ agent: {
143
+ '@type': 'WebAPI',
144
+ name: this.state.name,
145
+ identifier: this.state.ipfsId
146
+ },
147
+ resultComment: {
148
+ '@type': 'Comment',
149
+ text: message
150
+ }
151
+ }
152
+ }
153
+
154
+ return retObj
155
+ }
156
+ }
157
+
158
+ export default Schema
@@ -0,0 +1,443 @@
1
+ /*
2
+ Use Cases library for the thisNode entity.
3
+ */
4
+
5
+ import ThisNodeEntity from '../entities/this-node-entity.js'
6
+ import Schema from './schema.js'
7
+
8
+ // A global variable that maintains scope to the instance of this class, when
9
+ // the context of 'this' is lost.
10
+ let _this
11
+
12
+ class ThisNodeUseCases {
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 thisNode Use Cases library.'
19
+ )
20
+ }
21
+
22
+ // Optional JSON-LD used for announcements. If present, will override
23
+ // default announcement object in Schema library.
24
+ this.announceJsonLd = localConfig.announceJsonLd
25
+
26
+ // If consuming app wants to configure itself as a Circuit Relay, it can
27
+ // override the default value of false.
28
+ this.isCircuitRelay = localConfig.isCircuitRelay
29
+ if (!this.isCircuitRelay) this.isCircuitRelay = false
30
+
31
+ // Additional information for connecting to the circuit relay.
32
+ this.circuitRelayInfo = localConfig.circuitRelayInfo
33
+
34
+ _this = this
35
+ }
36
+
37
+ // Update this instance with copies of the other Use Case libraries.
38
+ updateUseCases (useCaseParent) {
39
+ this.useCases = {
40
+ relays: useCaseParent.relays,
41
+ pubsub: useCaseParent.pubsub,
42
+ peer: useCaseParent.peer
43
+ }
44
+ }
45
+
46
+ // Create an instance of the 'self' of thisNode. This function aggregates
47
+ // a lot of information pulled from the different adapters.
48
+ async createSelf (initValues = {}) {
49
+ const selfData = {
50
+ // The type of IPFS node this is: browser or node.js
51
+ type: initValues.type
52
+ }
53
+
54
+ // Aggregate data from the IPFS adapter.
55
+ selfData.ipfsId = this.adapters.ipfs.ipfsPeerId
56
+ selfData.ipfsMultiaddrs = this.adapters.ipfs.ipfsMultiaddrs
57
+
58
+ // Aggregate data from the BCH adapter.
59
+ const bchData = await this.adapters.bch.generateBchId()
60
+ selfData.bchAddr = bchData.cashAddress
61
+ selfData.slpAddr = bchData.slpAddress
62
+ selfData.publicKey = bchData.publicKey
63
+ // selfData.mnemonic = this.adapters.bch.mnemonic
64
+
65
+ // Generate an OrbitDB for other peers to pass messages to this node.
66
+ // TODO: deprecate
67
+ // selfData.orbit = await this.adapters.orbit.createRcvDb(selfData)
68
+
69
+ const schemaConfig = {
70
+ ipfsId: selfData.ipfsId,
71
+ type: selfData.type,
72
+ ipfsMultiaddrs: selfData.ipfsMultiaddrs,
73
+ isCircuitRelay: this.isCircuitRelay,
74
+ circuitRelayInfo: this.circuitRelayInfo,
75
+ cashAddress: selfData.bchAddr,
76
+ slpAddress: selfData.slpAddr,
77
+ publicKey: selfData.publicKey,
78
+ // orbitdbId: selfData.orbit.id,
79
+ // apiInfo: '',
80
+ announceJsonLd: this.announceJsonLd
81
+ }
82
+ selfData.schema = new Schema(schemaConfig)
83
+
84
+ // console.log('selfData: ', selfData)
85
+
86
+ // Create the thisNode entity
87
+ const thisNode = new ThisNodeEntity(selfData)
88
+ this.thisNode = thisNode
89
+
90
+ // Attach ther useCases (which includes adapters and controllers) to the
91
+ // thisNode entity.
92
+ this.thisNode.useCases = this.useCases
93
+
94
+ // Subscribe to my own pubsub channel, for receiving info from other peers.
95
+ selfData.pubsub = await this.adapters.pubsub.subscribeToPubsubChannel(
96
+ selfData.ipfsId,
97
+ this.tempHander,
98
+ this.thisNode
99
+ )
100
+
101
+ return thisNode
102
+ }
103
+
104
+ // This is an event handler that is triggered by a new announcement object
105
+ // being recieved on the general coordination pubsub channel.
106
+ // pubsub-use-cases.js/initializePubSub() depends on this function.
107
+ async addSubnetPeer (announceObj) {
108
+ try {
109
+ // console.log('announceObj: ', announceObj)
110
+
111
+ // Exit if the announcement object is stale.
112
+ if (!_this.isFreshPeer(announceObj)) return
113
+
114
+ const thisPeerId = announceObj.from.toString()
115
+ _this.adapters.log.statusLog(
116
+ 2,
117
+ `announcement recieved from ${thisPeerId}`
118
+ )
119
+ _this.adapters.log.statusLog(
120
+ 3,
121
+ `announcement recieved from ${thisPeerId}: `,
122
+ announceObj
123
+ )
124
+
125
+ // console.log('_this.thisNode.peerList: ', _this.thisNode.peerList)
126
+
127
+ // Add a timestamp.
128
+ const now = new Date()
129
+ announceObj.data.updatedAt = now.toISOString()
130
+
131
+ // If the peer is not already in the list of known peers, then add it.
132
+ if (!_this.thisNode.peerList.includes(thisPeerId)) {
133
+ _this.adapters.log.statusLog(0, `New peer found: ${thisPeerId}`)
134
+
135
+ // Add this peer to the list of subnet peers tracked by this node.
136
+ _this.thisNode.peerList.push(thisPeerId)
137
+
138
+ // Add the announcement data object to the peerData array tracked by This Node.
139
+ _this.thisNode.peerData.push(announceObj)
140
+
141
+ // Subscribe to pubsub channel for private messages to peer.
142
+ // Ignore any messages on this channel, since it is only used for
143
+ // broadcasting encrypted messages to the new peer, and they will
144
+ // respond on our own channel.
145
+ await _this.adapters.ipfs.ipfs.pubsub.subscribe(
146
+ thisPeerId,
147
+ async msg => {
148
+ }
149
+ )
150
+
151
+ // If the new peer has the isCircuitRelay flag set, then try to add it
152
+ // to the list of Circuit Relays.
153
+ if (announceObj.data.isCircuitRelay) {
154
+ // console.log('_this.thisNode: ', _this.thisNode)
155
+ await _this.useCases.relays.addRelay(thisPeerId, _this.thisNode)
156
+ }
157
+
158
+ // Subscribe to the private OrbitDB for this peer.
159
+ // await _this.adapters.orbit.connectToPeerDb({
160
+ // peerId: announceObj.from,
161
+ // orbitdbId: announceObj.data.orbitdb,
162
+ // thisNode: _this.thisNode
163
+ // })
164
+ // _this.adapters.log.statusLog(
165
+ // 2,
166
+ // `Connected to peer private OrbitDB: ${announceObj.data.orbitdb}`
167
+ // )
168
+ } else {
169
+ // Peer already exists in the list.
170
+ // console.log(`debug: Updating existing peer: ${thisPeerId}`)
171
+
172
+ // Get the data for this peer.
173
+ let thisPeerData = _this.thisNode.peerData.filter(
174
+ x => x.from === thisPeerId
175
+ )
176
+ thisPeerData = thisPeerData[0]
177
+ const dataIndex = _this.thisNode.peerData.indexOf(thisPeerData)
178
+ // console.log(`dataIndex: ${dataIndex}`)
179
+
180
+ // If the new announceObj is older than the last announceObj, then
181
+ // ignore it.
182
+ const oldBroadcastDate = new Date(thisPeerData.data.broadcastedAt)
183
+ const newBroadcastDate = new Date(announceObj.data.broadcastedAt)
184
+ if (newBroadcastDate.getTime() < oldBroadcastDate.getTime()) {
185
+ return true
186
+ }
187
+
188
+ // Replace the old data with the new data.
189
+ _this.thisNode.peerData[dataIndex] = announceObj
190
+
191
+ // console.log('announceObj.data.orbitdb: ', announceObj.data.orbitdb)
192
+ // console.log('thisPeerData: ', thisPeerData)
193
+
194
+ // Update the connection to the peers OrbitDB, if needed.
195
+ // const announceOrbitId = announceObj.data.orbitdb
196
+ // const peerOrbitId = thisPeerData.data.orbitdb
197
+ // if (!announceOrbitId.includes(peerOrbitId)) {
198
+ // // console.log(`debug: Switching to new OrbitDB for known peer: ${announceOrbitId}`)
199
+ // _this.adapters.log.statusLog(
200
+ // 0,
201
+ // `debug: Switching to new OrbitDB for known peer: ${announceOrbitId}`
202
+ // )
203
+ // // console.log(
204
+ // // `debug: announceObj: ${JSON.stringify(announceObj, null, 2)}`
205
+ // // )
206
+ //
207
+ // await _this.adapters.orbit.connectToPeerDb({
208
+ // peerId: announceObj.from,
209
+ // orbitdbId: announceObj.data.orbitdb,
210
+ // thisNode: _this.thisNode
211
+ // })
212
+ // }
213
+ }
214
+
215
+ return true
216
+ } catch (err) {
217
+ console.error('Error in this-node-use-cases.js/addSubnetPeer(): ', err)
218
+
219
+ _this.adapters.log.statusLog(
220
+ 2,
221
+ 'Error in this-node-use-cases.js/addSubnetPeer(): ',
222
+ err
223
+ )
224
+
225
+ // Do not throw an error. This is a top-level function called by the
226
+ // pubsub handler for the general coordination channel.
227
+ }
228
+ }
229
+
230
+ // Detects if a peer has gone 'stale' (inactive), or is still 'fresh' (active).
231
+ // Stale means it hasn't updated it's
232
+ // broadcastedAt property in more than 10 minutes.
233
+ // Return true if the peer is 'fresh', and false if 'stale'.
234
+ isFreshPeer (announceObj) {
235
+ try {
236
+ // Ignore announcements that do not have a broadcastedAt timestamp.
237
+ if (!announceObj.data.broadcastedAt) return false
238
+
239
+ // Ignore items that are older than 10 minutes.
240
+ const now = new Date()
241
+ const broadcastTime = new Date(announceObj.data.broadcastedAt)
242
+ const tenMinutes = 60000 * 10
243
+ const timeDiff = now.getTime() - broadcastTime.getTime()
244
+ if (timeDiff > tenMinutes) return false
245
+
246
+ return true
247
+ } catch (err) {
248
+ console.error('Error in stalePeer()')
249
+ throw err
250
+ }
251
+ }
252
+
253
+ // Called by an Interval, ensures connections are maintained to known pubsub
254
+ // peers. This will heal connections if nodes drop in and out of the network.
255
+ async refreshPeerConnections () {
256
+ try {
257
+ // console.log('this.thisNode: ', this.thisNode)
258
+
259
+ // const relays = this.cr.state.relays
260
+ const relays = this.thisNode.relayData
261
+ const peers = this.thisNode.peerList
262
+ // console.log('peers: ', peers)
263
+
264
+ // Get connected peers
265
+ const connectedPeers = await this.adapters.ipfs.getPeers()
266
+ // console.log('connectedPeers: ', connectedPeers)
267
+
268
+ // Loop through each known peer
269
+ for (let i = 0; i < peers.length; i++) {
270
+ // const thisPeer = this.state.peers[peer]
271
+ const thisPeer = peers[i]
272
+ // console.log(`thisPeer: ${JSON.stringify(thisPeer, null, 2)}`)
273
+
274
+ // Check if target peer is currently conected to the node.
275
+ const connectedPeer = connectedPeers.filter(
276
+ peerObj => peerObj.peer === thisPeer
277
+ )
278
+ // console.log('connectedPeer: ', connectedPeer)
279
+
280
+ // If this node is already connected to the peer, then skip this peers.
281
+ // We do not need to do anything.
282
+ if (connectedPeer.length) {
283
+ this.adapters.log.statusLog(
284
+ 2,
285
+ `Skipping peer in refreshPeerConnections(). Already connected to peer ${thisPeer}`
286
+ )
287
+ continue
288
+ }
289
+
290
+ // Get the peer data for the current peer.
291
+ let peerData = this.thisNode.peerData.filter(x => x.from.includes(thisPeer)
292
+ )
293
+ peerData = peerData[0]
294
+ // console.log('peerData: ', peerData)
295
+
296
+ // TODO: if broadcastedAt value is older than 10 minutes, skip connecting
297
+ // to the peer. It may be stale information.
298
+ if (!this.isFreshPeer(peerData)) {
299
+ this.adapters.log.statusLog(
300
+ 2,
301
+ `Peer ${peerData.from} is stale. Skipping.`
302
+ )
303
+ continue
304
+ }
305
+
306
+ // Sort the Circuit Relays by the average of the aboutLatency
307
+ // array. Connect to peers through the Relays with the lowest latencies
308
+ // first.
309
+ const sortedRelays = this.thisNode.useCases.relays.sortRelays(relays)
310
+ // console.log(`sortedRelays: ${JSON.stringify(sortedRelays, null, 2)}`)
311
+
312
+ // Loop through each known circuit relay and attempt to connect to the
313
+ // peer through a relay.
314
+ for (let i = 0; i < sortedRelays.length; i++) {
315
+ const thisRelay = sortedRelays[i]
316
+ // console.log(`thisRelay: ${JSON.stringify(thisRelay, null, 2)}`)
317
+
318
+ // Generate a multiaddr for connecting to the peer through a circuit relay.
319
+ const multiaddr = `${thisRelay.multiaddr}/p2p-circuit/p2p/${thisPeer}`
320
+ // console.log(`multiaddr: ${multiaddr}`)
321
+
322
+ // Skip the relay if this node is not connected to it.
323
+ if (thisRelay.connected) {
324
+ // Attempt to connect to the node through a circuit relay.
325
+ const connected = await this.adapters.ipfs.connectToPeer(multiaddr)
326
+
327
+ // If the connection was successful, break out of the relay loop.
328
+ // Otherwise try to connect through the next relay.
329
+ if (connected) {
330
+ // this.adapters.log.statusLog(0,
331
+ // 'Successfully connected to peer through circuit relay.'
332
+ // )
333
+
334
+ // Break out of the loop once we've made a successful connection.
335
+ break
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ const now = new Date()
342
+ this.adapters.log.statusLog(
343
+ 2,
344
+ `Renewed connections to all known peers at ${now.toLocaleString()}`
345
+ )
346
+
347
+ return true
348
+ } catch (err) {
349
+ console.error('Error in refreshPeerConnections()')
350
+ throw err
351
+ }
352
+ }
353
+
354
+ // Enforce the blacklist by actively disconnecting from nodes in the blacklist.
355
+ async enforceBlacklist () {
356
+ try {
357
+ const blacklistPeers = this.thisNode.blacklistPeers
358
+ for (let i = 0; i < blacklistPeers.length; i++) {
359
+ const ipfsId = blacklistPeers[i]
360
+
361
+ await this.adapters.ipfs.disconnectFromPeer(ipfsId)
362
+ }
363
+
364
+ const blacklistMultiaddrs = this.thisNode.blacklistMultiaddrs
365
+ for (let i = 0; i < blacklistMultiaddrs.length; i++) {
366
+ const multiaddr = blacklistMultiaddrs[i]
367
+
368
+ await this.adapters.ipfs.disconnectFromMultiaddr(multiaddr)
369
+ }
370
+
371
+ return true
372
+ } catch (err) {
373
+ console.error('Error in enforceBlacklist()')
374
+ throw err
375
+ }
376
+ }
377
+
378
+ // An alternative to the blacklist, it's a whitelist, where all nodes are
379
+ // disconnected except ones that have a 'name' property. This ensures that
380
+ // the node only connects to other nodes that are using ipfs-coord.
381
+ async enforceWhitelist () {
382
+ try {
383
+ let showDebugData = false
384
+
385
+ // Get all peers.
386
+ const allPeers = await this.adapters.ipfs.getPeers()
387
+ // console.log(`allPeers: ${JSON.stringify(allPeers, null, 2)}`)
388
+
389
+ // Get ipfs-coord peers.
390
+ const coordPeers = this.thisNode.peerData
391
+ // console.log(`coordPeers: ${JSON.stringify(coordPeers, null, 2)}`)
392
+
393
+ // Try to match each peer up with ipfs-coord info.
394
+ // Add the name from the ipfs-coord info.
395
+ for (let i = 0; i < allPeers.length; i++) {
396
+ const thisPeer = allPeers[i]
397
+ thisPeer.name = ''
398
+
399
+ // If IPFS peer exists in the ipfs-coord peer list, then foundPeer
400
+ // will be an array with 1 element.
401
+ const foundPeer = coordPeers.filter(x => x.from.toString().includes(thisPeer.peer.toString())
402
+ )
403
+
404
+ // If a connected peer matches an ipfs-coord peer, add the 'name'
405
+ // property to it.
406
+ if (!foundPeer.length) {
407
+ this.adapters.log.statusLog(
408
+ 3,
409
+ `Whitelist enforcement disconnecting peer ${thisPeer.peer.toString()}`
410
+ )
411
+
412
+ showDebugData = true
413
+
414
+ // If a connected peer can not be matched with the list of ipfs-coord
415
+ // peers, then disconnect from it.
416
+ await this.adapters.ipfs.disconnectFromPeer(thisPeer.peer.toString())
417
+ }
418
+ }
419
+
420
+ // Show the debugging data once, instead of inside the for-loop, which would
421
+ // show the debugging data each time a peer is disconnected.
422
+ if (showDebugData) {
423
+ // Deep debugging.
424
+ this.adapters.log.statusLog(
425
+ 3,
426
+ `allPeers: ${JSON.stringify(allPeers, null, 2)}`
427
+ )
428
+ this.adapters.log.statusLog(
429
+ 3,
430
+ `coordPeers: ${JSON.stringify(coordPeers, null, 2)}`
431
+ )
432
+ }
433
+
434
+ return true
435
+ } catch (err) {
436
+ console.error('Error in enforceWhitelist()')
437
+ throw err
438
+ }
439
+ }
440
+ }
441
+
442
+ // module.exports = ThisNodeUseCases
443
+ export default ThisNodeUseCases
@@ -0,0 +1,12 @@
1
+ /*
2
+ This utility library contains common functions that are used throughout
3
+ the program.
4
+ */
5
+
6
+ class Util {
7
+ sleep (ms) {
8
+ return new Promise(resolve => setTimeout(resolve, ms))
9
+ }
10
+ }
11
+
12
+ export default Util
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "helia-coord",
3
+ "version": "1.1.0",
4
+ "description": "A JS library for helping IPFS peers coordinate, find a common interest, and stay connected around that interest.",
5
+ "main": "./index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "npm run lint && cross-env 'NODE_OPTIONS=--experimental-loader @istanbuljs/esm-loader-hook' nyc mocha --recursive --exit test/unit/",
9
+ "lint": "standard --env mocha --fix",
10
+ "coverage:report": "cross-env 'NODE_OPTIONS=--experimental-loader @istanbuljs/esm-loader-hook' nyc --reporter=html mocha test/unit/ --recursive --exit"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Permissionless-Software-Foundation/helia-coord.git"
15
+ },
16
+ "author": "Chris Troutner <chris.troutner@gmail.com>",
17
+ "license": "MIT",
18
+ "bugs": {
19
+ "url": "https://github.com/Permissionless-Software-Foundation/helia-coord/issues"
20
+ },
21
+ "homepage": "https://github.com/Permissionless-Software-Foundation/helia-coord#readme",
22
+ "devDependencies": {
23
+ "@istanbuljs/esm-loader-hook": "0.2.0",
24
+ "chai": "4.3.6",
25
+ "cross-env": "7.0.3",
26
+ "lodash.clonedeep": "4.5.0",
27
+ "minimal-slp-wallet": "5.8.9",
28
+ "mocha": "10.0.0",
29
+ "nyc": "15.1.0",
30
+ "sinon": "14.0.0",
31
+ "standard": "17.0.0"
32
+ },
33
+ "dependencies": {
34
+ "bch-encrypt-lib": "2.0.0",
35
+ "uuid": "9.0.0"
36
+ },
37
+ "peerDependencies": {
38
+ "ipfs-http-client": ">= 58.0.0"
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "import": {
43
+ "browser": "./index.js",
44
+ "node": "./index.js",
45
+ "default": "./index.js"
46
+ },
47
+ "require": {
48
+ "default": "./index.js"
49
+ }
50
+ }
51
+ }
52
+ }