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,275 @@
1
+ /*
2
+ Adapter library for working with pubsub channels and messages
3
+ */
4
+
5
+ // Local libraries
6
+ import Messaging from './messaging.js'
7
+ import AboutAdapter from './about-adapter.js'
8
+ import { BroadcastRouter, PrivateChannelRouter } from './msg-router.js'
9
+
10
+ class PubsubAdapter {
11
+ constructor (localConfig = {}) {
12
+ // Dependency Injection
13
+ this.ipfs = localConfig.ipfsAdapter
14
+ if (!this.ipfs) {
15
+ console.log('this.ipfs: ', this.ipfs)
16
+ throw new Error(
17
+ 'Instance of IPFS adapter required when instantiating Pubsub Adapter.'
18
+ )
19
+ }
20
+ this.log = localConfig.log
21
+ if (!this.log) {
22
+ throw new Error(
23
+ 'A status log handler function required when instantitating Pubsub Adapter'
24
+ )
25
+ }
26
+ this.encryption = localConfig.encryption
27
+ if (!this.encryption) {
28
+ throw new Error(
29
+ 'An instance of the encryption Adapter must be passed when instantiating the Pubsub Adapter library.'
30
+ )
31
+ }
32
+ this.privateLog = localConfig.privateLog
33
+ if (!this.privateLog) {
34
+ throw new Error(
35
+ 'A private log handler must be passed when instantiating the Pubsub Adapter library.'
36
+ )
37
+ }
38
+
39
+ // Encapsulate dependencies
40
+ this.messaging = new Messaging(localConfig)
41
+ this.about = new AboutAdapter(localConfig)
42
+
43
+ // 'embedded' node type used as default, will use embedded js-ipfs.
44
+ // Alternative is 'external' which will use ipfs-http-client to control an
45
+ // external IPFS node.
46
+ this.nodeType = localConfig.nodeType
47
+ if (!this.nodeType) {
48
+ // console.log('No node type specified. Assuming embedded js-ipfs.')
49
+ this.nodeType = 'embedded'
50
+ }
51
+ // console.log(`PubsubAdapter contructor node type: ${this.nodeType}`)
52
+
53
+ // Bind functions that are called by event handlers
54
+ this.parsePubsubMessage = this.parsePubsubMessage.bind(this)
55
+ this.handleNewMessage = this.handleNewMessage.bind(this)
56
+ }
57
+
58
+ // Subscribe to a pubsub channel. Any data received on that channel is passed
59
+ // to the handler.
60
+ async subscribeToPubsubChannel (chanName, handler, thisNode) {
61
+ try {
62
+ // console.log('thisNode: ', thisNode)
63
+ const thisNodeId = thisNode.ipfsId
64
+
65
+ // Normal use-case, where the pubsub channel is NOT the receiving channel
66
+ // for this node. This applies to general broadcast channels like
67
+ // the coordination channel that all nodes use to annouce themselves.
68
+ if (chanName !== thisNodeId) {
69
+ // console.log('this.ipfs: ', this.ipfs)
70
+ // await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), async (msg) => {
71
+ // await this.parsePubsubMessage(msg, handler, thisNode)
72
+ // })
73
+
74
+ // Instantiate the Broadcast message router library
75
+ const bRouterOptions = {
76
+ handler,
77
+ thisNode,
78
+ parsePubsubMessage: this.parsePubsubMessage
79
+ }
80
+ const broadcastRouter = new BroadcastRouter(bRouterOptions)
81
+
82
+ // Subscribe to the pubsub channel. Route any incoming messages to the
83
+ // appropriate handler.
84
+ // await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), broadcastRouter.route)
85
+ await this.ipfs.ipfs.libp2p.services.pubsub.subscribe(chanName.toString(), broadcastRouter.route)
86
+
87
+ //
88
+ } else {
89
+ // Subscribing to our own pubsub channel. This is the channel other nodes
90
+ // will use to send RPC commands and send private messages.
91
+
92
+ // Instantiate the Broadcast message router library
93
+ const pRouterOptions = {
94
+ thisNode,
95
+ messaging: this.messaging,
96
+ handleNewMessage: this.handleNewMessage
97
+ }
98
+ const privateRouter = new PrivateChannelRouter(pRouterOptions)
99
+
100
+ // Subscribe to the pubsbu channel. Route any incoming messages to the
101
+ // this library.
102
+ // await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), privateRouter.route)
103
+ await this.ipfs.ipfs.libp2p.services.pubsub.subscribe(chanName.toString(), privateRouter.route)
104
+
105
+ // await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), async (msg) => {
106
+ // const msgObj = await this.messaging.handleIncomingData(msg, thisNode)
107
+ //
108
+ // // If msgObj is false, then ignore it. Typically indicates an already
109
+ // // processed message.
110
+ // if (msgObj) { await this.handleNewMessage(msgObj, thisNode) }
111
+ // })
112
+ }
113
+
114
+ this.log.statusLog(
115
+ 0,
116
+ `status: Subscribed to pubsub channel: ${chanName}`
117
+ )
118
+
119
+ return true
120
+ } catch (err) {
121
+ console.error('Error in subscribeToPubsubChannel()')
122
+ throw err
123
+ }
124
+ }
125
+
126
+ // After the messaging.js library does the lower-level message handling and
127
+ // decryption, it passes the message on to this function, which does any
128
+ // additional parsing needed, and
129
+ // then routes the parsed data on to the user-specified handler.
130
+ async handleNewMessage (msgObj, thisNode) {
131
+ try {
132
+ // console.log('handleNewMessage() will forward this data onto the handler: ', msgObj)
133
+
134
+ // Check to see if this is metrics data or user-requested data.
135
+ // If it the response to a metrics query, trigger the handler for that.
136
+ // Dev note: This only handles the response. The JSON RPC must pass
137
+ // through this function to the privateLog, to be handled by the service
138
+ // being measured.
139
+ const isAbout = await this.captureMetrics(msgObj.data.payload, msgObj.from, thisNode)
140
+
141
+ // Pass the JSON RPC data to the private log to be handled by the app
142
+ // consuming this library.
143
+ if (!isAbout) {
144
+ // console.log('handleNewMessage() forwarding payload on to handler.')
145
+ this.privateLog(msgObj.data.payload, msgObj.from)
146
+
147
+ return true
148
+ }
149
+
150
+ return false
151
+ } catch (err) {
152
+ console.error('Error in handleNewMessage()')
153
+ throw err
154
+ }
155
+ }
156
+
157
+ // Scans input data. If the data is determined to be an 'about' JSON RPC
158
+ // reponse used for metrics, then the relayMetrics event is triggered and
159
+ // true is returned. Otherwise, false is returned.
160
+ async captureMetrics (decryptedStr, from, thisNode) {
161
+ try {
162
+ // console.log('decryptedStr: ', decryptedStr)
163
+ // console.log('thisNode: ', thisNode)
164
+
165
+ const data = JSON.parse(decryptedStr)
166
+ // console.log('data: ', data)
167
+
168
+ // Handle /about JSON RPC queries.
169
+ if (data.id.includes('metrics') && data.method === 'about') {
170
+ // Request recieved, send response.
171
+
172
+ // console.log('/about JSON RPC captured. Sending back announce object.')
173
+
174
+ // console.log('thisNode.schema.state.announceJsonLd: ', thisNode.schema.state.announceJsonLd)
175
+
176
+ const jsonResponse = `{"jsonrpc": "2.0", "id": "${data.id}", "result": {"method": "about", "receiver": "${from}", "value": ${JSON.stringify(thisNode.schema.state)}}}`
177
+ // console.log(`Responding with this JSON RPC response: ${jsonResponse}`)
178
+
179
+ // Encrypt the string with the peers public key.
180
+ const peerData = thisNode.peerData.filter(x => x.from === from)
181
+ const payload = await this.encryption.encryptMsg(
182
+ peerData[0],
183
+ jsonResponse
184
+ )
185
+
186
+ await this.messaging.sendMsg(from, payload, thisNode)
187
+
188
+ return true
189
+
190
+ //
191
+ } else if (data.id.includes('metrics') && data.result && data.result.method === 'about') {
192
+ // Response received.
193
+
194
+ // console.log('JSON RPC /about response aquired.')
195
+
196
+ // This event is handled by the about-adapter.js. It measures the
197
+ // latency between peers.
198
+ this.about.relayMetricsReceived(decryptedStr)
199
+
200
+ return data.result.value
201
+ }
202
+
203
+ // This is not an /about JSON RPC query.
204
+ // console.log('JSON RPC is not targeting the /about endpoint')
205
+ return false
206
+ } catch (err) {
207
+ console.error('Error in captureMetrics: ', err)
208
+ return false
209
+ }
210
+ }
211
+
212
+ // Attempts to parse data coming in from a pubsub channel. It is assumed that
213
+ // the data is a string in JSON format. If it isn't, parsing will throw an
214
+ // error and the message will be ignored.
215
+ async parsePubsubMessage (msg, handler, thisNode) {
216
+ try {
217
+ // console.log('message data: ', msg.data)
218
+ // console.log('parsePubsubMessage msg: ', msg)
219
+ // console.log('thisNode: ', thisNode)
220
+
221
+ const thisNodeId = thisNode.ipfsId
222
+
223
+ // Get data about the message.
224
+ const from = msg.from.toString()
225
+ const channel = msg.topic
226
+
227
+ // Used for debugging.
228
+ this.log.statusLog(
229
+ 2,
230
+ `Broadcast pubsub message recieved from ${from} on channel ${channel}`
231
+ )
232
+
233
+ // Ignore this message if it originated from this IPFS node.
234
+ if (from === thisNodeId) return true
235
+
236
+ // The data on browsers comes through as a uint8array, and on node.js
237
+ // implementiong it comes through as a string. Browsers need to
238
+ // convert the message from a uint8array to a string.
239
+ // console.log('this.nodeType: ', this.nodeType)
240
+ if (thisNode.type === 'browser' || this.nodeType === 'external') {
241
+ // console.log('Node type is browser or external')
242
+ msg.data = new TextDecoder('utf-8').decode(msg.data)
243
+ }
244
+ // console.log('msg.data: ', JSON.parse(msg.data.toString()))
245
+
246
+ let data
247
+ try {
248
+ // Parse the data into a JSON object. It starts as a Buffer that needs
249
+ // to be converted to a string, then parsed to a JSON object.
250
+ // For some reason I have to JSON parse it twice. Not sure why.
251
+ data = JSON.parse(JSON.parse(msg.data.toString()))
252
+ // console.log('data: ', data)
253
+ } catch (err) {
254
+ throw new Error(`Failed to parse JSON in message from ${from} in pubsub channel ${channel}.`)
255
+ }
256
+
257
+ const retObj = { from, channel, data }
258
+ // console.log(`new pubsub message received: ${JSON.stringify(retObj, null, 2)}`)
259
+
260
+ // Hand retObj to the callback.
261
+ handler(retObj)
262
+
263
+ return true
264
+ } catch (err) {
265
+ // console.error('Error in parsePubsubMessage(): ', err.message)
266
+ this.log.statusLog(2, `Error in parsePubsubMessage(): ${err.message}`)
267
+ // Do not throw an error. This is a top-level function.
268
+
269
+ return false
270
+ }
271
+ }
272
+ }
273
+
274
+ // module.exports = PubsubAdapter
275
+ export default PubsubAdapter
@@ -0,0 +1,389 @@
1
+ /*
2
+ A library for broadcasting messages over pubsub and managing 'lost messages'.
3
+ */
4
+
5
+ // Global npm libraries
6
+ import { v4 as uid } from 'uuid'
7
+
8
+ // Local libraries
9
+ import ResendMsg from './resend-msg.js'
10
+
11
+ // Constants
12
+ const TIME_BETWEEN_RETRIES = 5000 // time in milliseconds
13
+ // const RETRY_LIMIT = 3
14
+
15
+ class Messaging {
16
+ constructor (localConfig = {}) {
17
+ // Dependency Injection
18
+ this.ipfs = localConfig.ipfsAdapter
19
+ if (!this.ipfs) {
20
+ throw new Error(
21
+ 'Instance of IPFS adapter required when instantiating Messaging Adapter.'
22
+ )
23
+ }
24
+ this.log = localConfig.log
25
+ if (!this.log) {
26
+ throw new Error(
27
+ 'A status log handler function required when instantitating Messaging Adapter'
28
+ )
29
+ }
30
+ this.encryption = localConfig.encryption
31
+ if (!this.encryption) {
32
+ throw new Error(
33
+ 'An instance of the encryption Adapter must be passed when instantiating the Messaging Adapter library.'
34
+ )
35
+ }
36
+
37
+ // 'embedded' node type used as default, will use embedded js-ipfs.
38
+ // Alternative is 'external' which will use ipfs-http-client to control an
39
+ // external IPFS node.
40
+ this.nodeType = localConfig.nodeType
41
+ if (!this.nodeType) {
42
+ // console.log('No node type specified. Assuming embedded js-ipfs.')
43
+ this.nodeType = 'embedded'
44
+ }
45
+
46
+ // Encapsulate dependencies
47
+ this.uid = uid
48
+
49
+ // State
50
+ this.msgQueue = []
51
+ // Cache to store UUIDs of processed messages. Used to prevent duplicate
52
+ // processing.
53
+ this.msgCache = []
54
+ this.MSG_CACHE_SIZE = 30
55
+ }
56
+
57
+ // Send a message to a peer
58
+ // The message is added to a queue that will automatically track ACK messages
59
+ // and re-send the message if an ACK message is not received.
60
+ async sendMsg (receiver, payload, thisNode) {
61
+ try {
62
+ // console.log(`sendMsg thisNode: `, thisNode)
63
+
64
+ // Generate a message object
65
+ const sender = thisNode.ipfsId
66
+ const inMsgObj = {
67
+ sender,
68
+ receiver,
69
+ payload
70
+ }
71
+ const msgObj = this.generateMsgObj(inMsgObj)
72
+
73
+ // const msgId = msgObj.uuid
74
+
75
+ // Send message
76
+ await this.publishToPubsubChannel(receiver, msgObj)
77
+
78
+ // Add the message to the retry queue
79
+ this.addMsgToQueue(msgObj)
80
+
81
+ return true
82
+ } catch (err) {
83
+ console.error('Error in messaging.js/sendMsg()')
84
+ throw err
85
+ }
86
+ }
87
+
88
+ // Publish an ACK (acknowldge) message. Does not wait for any reply. Just fires
89
+ // and returns.
90
+ async sendAck (data, thisNode) {
91
+ try {
92
+ const ackMsgObj = await this.generateAckMsg(data, thisNode)
93
+
94
+ // Send Ack message
95
+ await this.publishToPubsubChannel(data.sender, ackMsgObj)
96
+
97
+ return true
98
+ } catch (err) {
99
+ console.error('Error in sendAck()')
100
+ throw err
101
+ }
102
+ }
103
+
104
+ // A handler function that is called when a new message is recieved on the
105
+ // pubsub channel for this IPFS node. It does the following:
106
+ // - Decrypts the message.
107
+ // - Sends an ACK message to the sender of the message.
108
+ // - Returns an object containing the message and metadata.
109
+ async handleIncomingData (msg, thisNode) {
110
+ try {
111
+ // console.log('handleIncomingData() msg: ', msg)
112
+ // console.log('thisNode: ', thisNode)
113
+
114
+ const thisNodeId = thisNode.ipfsId
115
+
116
+ // Get data about the message.
117
+ const from = msg.from.toString()
118
+ const channel = msg.topic
119
+
120
+ // Ignore this message if it originated from this IPFS node.
121
+ if (from === thisNodeId) return false
122
+
123
+ // The data on browsers comes through as a uint8array, and on node.js
124
+ // implementiong it comes through as a string when using js-ipfs. But when
125
+ // using ipfs-http-client with an external go-ipfs node, it comes thorugh
126
+ // as a uint8Array too. Browsers need to
127
+ // convert the message from a uint8array to a string.
128
+ if (thisNode.type === 'browser' || this.nodeType === 'external') {
129
+ // console.log('Node type is browser')
130
+ msg.data = new TextDecoder('utf-8').decode(msg.data)
131
+ }
132
+ // console.log('msg.data: ', msg.data)
133
+
134
+ // Parse the data into a JSON object. It starts as a Buffer that needs
135
+ // to be converted to a string, then parsed to a JSON object.
136
+ // For some reason I have to JSON parse it twice. Not sure why.
137
+ const data = JSON.parse(msg.data.toString())
138
+ // console.log('message data: ', data)
139
+
140
+ // Decrypt the payload
141
+ const decryptedPayload = await this.encryption.decryptMsg(data.payload)
142
+ // console.log(`decrypted payload: ${decryptedPayload}`)
143
+
144
+ // Filter ACK messages from other messages
145
+ if (decryptedPayload.includes('"apiName":"ACK"')) {
146
+ this.log.statusLog(2, `ACK message received for ${data.uuid}`)
147
+
148
+ this.delMsgFromQueue(data)
149
+
150
+ return false
151
+ } else {
152
+ this.log.statusLog(
153
+ 2,
154
+ `Private pubsub message recieved from ${from} on channel ${channel} with message ID ${data.uuid}`
155
+ )
156
+ }
157
+
158
+ // Debug logs from an 'about' JSON RPC command.
159
+ if (decryptedPayload.includes('"id"')) {
160
+ const obj = JSON.parse(decryptedPayload)
161
+ // console.log(`debug log obj: ${JSON.stringify(obj, null, 2)}`)
162
+
163
+ if (obj.result) {
164
+ // Response
165
+ this.log.statusLog(2, `Message ID ${data.uuid} contains RPC response with ID ${obj.id}`)
166
+ } else {
167
+ // Request
168
+ this.log.statusLog(2, `Message ID ${data.uuid} contains RPC request with ID ${obj.id}`)
169
+ }
170
+ }
171
+
172
+ // Send an ACK message
173
+ await this.sendAck(data, thisNode)
174
+
175
+ // Ignore message if its already been processed.
176
+ const alreadyProcessed = this._checkIfAlreadyProcessed(data.uuid)
177
+ if (alreadyProcessed) {
178
+ console.log(`Message ${data.uuid} already processed`)
179
+ return false
180
+ }
181
+
182
+ // Replace the encrypted data with the decrypted data.
183
+ data.payload = decryptedPayload
184
+
185
+ const retObj = { from, channel, data }
186
+ // console.log(
187
+ // `new pubsub message received: ${JSON.stringify(retObj, null, 2)}`
188
+ // )
189
+
190
+ return retObj
191
+ } catch (err) {
192
+ console.error('Error in handleIncomingData(): ', err)
193
+ // throw err
194
+ // Do not throw an error. This is a top-level function called by an Interval.
195
+ return false
196
+ }
197
+ }
198
+
199
+ // Generate a message object with UUID and timestamp.
200
+ generateMsgObj (inMsgObj = {}) {
201
+ try {
202
+ const { sender, receiver, payload } = inMsgObj
203
+
204
+ // Input validation
205
+ if (!sender) {
206
+ throw new Error('Sender required when calling generateMsgObj()')
207
+ }
208
+ if (!receiver) {
209
+ throw new Error('Receiver required when calling generateMsgObj()')
210
+ }
211
+ if (!payload) {
212
+ throw new Error('Payload required when calling generateMsgObj()')
213
+ }
214
+
215
+ const uuid = this.uid()
216
+
217
+ const now = new Date()
218
+ const timestamp = now.toISOString()
219
+
220
+ const outMsgObj = {
221
+ timestamp,
222
+ uuid,
223
+ sender,
224
+ receiver,
225
+ payload
226
+ }
227
+
228
+ return outMsgObj
229
+ } catch (err) {
230
+ console.log('Error in generateMsgObj()')
231
+ throw err
232
+ }
233
+ }
234
+
235
+ // Generate an ACK (acknowledge) message.
236
+ async generateAckMsg (data, thisNode) {
237
+ try {
238
+ // console.log('thisNode: ', thisNode)
239
+
240
+ // The sender of the original messages is the receiver of the ACK message.
241
+ const receiver = data.sender
242
+ const uuid = data.uuid
243
+
244
+ const ackMsg = {
245
+ apiName: 'ACK'
246
+ }
247
+
248
+ const peerData = thisNode.peerData.filter(x => x.from === receiver)
249
+
250
+ // Encrypt the string with the peers public key.
251
+ const payload = await this.encryption.encryptMsg(
252
+ peerData[0],
253
+ JSON.stringify(ackMsg)
254
+ )
255
+
256
+ const sender = thisNode.ipfsId
257
+
258
+ const inMsgObj = {
259
+ sender,
260
+ receiver,
261
+ payload
262
+ }
263
+
264
+ const outMsgObj = this.generateMsgObj(inMsgObj)
265
+
266
+ // Replace the message UUID with the UUID from the original message.
267
+ outMsgObj.uuid = uuid
268
+
269
+ this.log.statusLog(
270
+ 2,
271
+ `Sending ACK message for ID ${uuid}`
272
+ )
273
+
274
+ return outMsgObj
275
+ } catch (err) {
276
+ console.error('Error in generateAckMsg()')
277
+ throw err
278
+ }
279
+ }
280
+
281
+ // Converts an input string to a Buffer and then broadcasts it to the given
282
+ // pubsub room.
283
+ async publishToPubsubChannel (chanName, msgObj) {
284
+ try {
285
+ const msgBuf = Buffer.from(JSON.stringify(msgObj))
286
+
287
+ // Publish the message to the pubsub channel.
288
+ // await this.ipfs.ipfs.pubsub.publish(chanName, msgBuf)
289
+ await this.ipfs.ipfs.libp2p.services.pubsub.publish(chanName, msgBuf)
290
+
291
+ // console.log('msgObj: ', msgObj)
292
+
293
+ // Used for debugging.
294
+ if (msgObj.uuid) {
295
+ this.log.statusLog(
296
+ 2,
297
+ `New message published to private channel ${chanName} with ID ${msgObj.uuid}`
298
+ )
299
+ } else {
300
+ this.log.statusLog(
301
+ 2,
302
+ `New announcement message published to broadcast channel ${chanName}`
303
+ )
304
+ }
305
+
306
+ return true
307
+ } catch (err) {
308
+ console.error('Error in publishToPubsubChannel()')
309
+ throw err
310
+ }
311
+ }
312
+
313
+ // Checks the UUID to see if the message has already been processed. Returns
314
+ // true if the UUID exists in the list of processed messages.
315
+ _checkIfAlreadyProcessed (uuid) {
316
+ // Check if the hash is in the array of already processed message.
317
+ const alreadyProcessed = this.msgCache.includes(uuid)
318
+
319
+ // Update the msgCache if this is a new message.
320
+ if (!alreadyProcessed) {
321
+ // Add the uuid to the array.
322
+ this.msgCache.push(uuid)
323
+
324
+ // If the array is at its max size, then remove the oldest element.
325
+ if (this.msgCache.length > this.MSG_CACHE_SIZE) {
326
+ this.msgCache.shift()
327
+ }
328
+ }
329
+
330
+ return alreadyProcessed
331
+ }
332
+
333
+ // Stops the Interval Timer and deletes a message from the queue when an
334
+ // ACK message is received.
335
+ delMsgFromQueue (msgObj) {
336
+ // Loop through the message queue
337
+ for (let i = 0; i < this.msgQueue.length; i++) {
338
+ const thisMsg = this.msgQueue[i]
339
+
340
+ // Find the matching entry.
341
+ if (msgObj.uuid === thisMsg.uuid) {
342
+ // console.log(`thisMsg: `, thisMsg)
343
+
344
+ // Stop the Interval
345
+ try {
346
+ clearInterval(thisMsg.intervalHandle)
347
+ // console.log('Interval stopped')
348
+ } catch (err) { /* exit quietly */ }
349
+
350
+ // Delete the entry from the msgQueue array.
351
+ this.msgQueue.splice(i, 1)
352
+ break
353
+ }
354
+ }
355
+
356
+ return true
357
+ }
358
+
359
+ // Adds a message object to the message queue. Starts an interval timer that
360
+ // will repeat the message periodically until an ACK message is received.
361
+ addMsgToQueue (msgObj = {}) {
362
+ try {
363
+ msgObj.retryCnt = 1
364
+
365
+ const resendMsg = new ResendMsg({ msgObj, msgLib: this })
366
+
367
+ // Start interval for repeating message
368
+ // const intervalHandle = setInterval(function () {
369
+ // _this.resendMsg(msgObj)
370
+ // }, TIME_BETWEEN_RETRIES)
371
+
372
+ const intervalHandle = setInterval(resendMsg.resend, TIME_BETWEEN_RETRIES)
373
+
374
+ // Add interval handle to message object.
375
+ msgObj.intervalHandle = intervalHandle
376
+
377
+ // Add message object to the queue.
378
+ this.msgQueue.push(msgObj)
379
+
380
+ return msgObj
381
+ } catch (err) {
382
+ console.error('Error in addMsgToQueue')
383
+ throw err
384
+ }
385
+ }
386
+ }
387
+
388
+ // module.exports = Messaging
389
+ export default Messaging