react-native-ble-mesh 1.1.1 → 2.0.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 (65) hide show
  1. package/README.md +288 -172
  2. package/docs/IOS-BACKGROUND-BLE.md +231 -0
  3. package/docs/OPTIMIZATION.md +70 -0
  4. package/docs/SPEC-v2.1.md +308 -0
  5. package/package.json +1 -1
  6. package/src/MeshNetwork.js +659 -465
  7. package/src/constants/index.js +1 -0
  8. package/src/crypto/AutoCrypto.js +79 -0
  9. package/src/crypto/CryptoProvider.js +99 -0
  10. package/src/crypto/index.js +15 -63
  11. package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
  12. package/src/crypto/providers/QuickCryptoProvider.js +134 -0
  13. package/src/crypto/providers/TweetNaClProvider.js +124 -0
  14. package/src/crypto/providers/index.js +11 -0
  15. package/src/errors/MeshError.js +2 -1
  16. package/src/expo/withBLEMesh.js +102 -0
  17. package/src/hooks/useMesh.js +30 -9
  18. package/src/hooks/useMessages.js +2 -0
  19. package/src/index.js +23 -8
  20. package/src/mesh/dedup/DedupManager.js +36 -10
  21. package/src/mesh/fragment/Assembler.js +5 -0
  22. package/src/mesh/index.js +1 -1
  23. package/src/mesh/monitor/ConnectionQuality.js +408 -0
  24. package/src/mesh/monitor/NetworkMonitor.js +327 -316
  25. package/src/mesh/monitor/index.js +7 -3
  26. package/src/mesh/peer/PeerManager.js +6 -1
  27. package/src/mesh/router/MessageRouter.js +26 -15
  28. package/src/mesh/router/RouteTable.js +7 -1
  29. package/src/mesh/store/StoreAndForwardManager.js +295 -297
  30. package/src/mesh/store/index.js +1 -1
  31. package/src/service/BatteryOptimizer.js +282 -278
  32. package/src/service/EmergencyManager.js +224 -214
  33. package/src/service/HandshakeManager.js +167 -13
  34. package/src/service/MeshService.js +72 -6
  35. package/src/service/SessionManager.js +77 -2
  36. package/src/service/audio/AudioManager.js +8 -2
  37. package/src/service/file/FileAssembler.js +106 -0
  38. package/src/service/file/FileChunker.js +79 -0
  39. package/src/service/file/FileManager.js +307 -0
  40. package/src/service/file/FileMessage.js +122 -0
  41. package/src/service/file/index.js +15 -0
  42. package/src/service/text/broadcast/BroadcastManager.js +16 -0
  43. package/src/transport/BLETransport.js +131 -9
  44. package/src/transport/MockTransport.js +1 -1
  45. package/src/transport/MultiTransport.js +305 -0
  46. package/src/transport/WiFiDirectTransport.js +295 -0
  47. package/src/transport/adapters/NodeBLEAdapter.js +34 -0
  48. package/src/transport/adapters/RNBLEAdapter.js +56 -1
  49. package/src/transport/index.js +6 -0
  50. package/src/utils/compression.js +291 -291
  51. package/src/crypto/aead.js +0 -189
  52. package/src/crypto/chacha20.js +0 -181
  53. package/src/crypto/hkdf.js +0 -187
  54. package/src/crypto/hmac.js +0 -143
  55. package/src/crypto/keys/KeyManager.js +0 -271
  56. package/src/crypto/keys/KeyPair.js +0 -216
  57. package/src/crypto/keys/SecureStorage.js +0 -219
  58. package/src/crypto/keys/index.js +0 -32
  59. package/src/crypto/noise/handshake.js +0 -410
  60. package/src/crypto/noise/index.js +0 -27
  61. package/src/crypto/noise/session.js +0 -253
  62. package/src/crypto/noise/state.js +0 -268
  63. package/src/crypto/poly1305.js +0 -113
  64. package/src/crypto/sha256.js +0 -240
  65. package/src/crypto/x25519.js +0 -154
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * @fileoverview Network Health Monitoring for BLE Mesh Network
5
5
  * @module mesh/monitor/NetworkMonitor
6
- *
6
+ *
7
7
  * Provides real-time metrics for mesh network status including
8
8
  * active nodes, latency, packet loss, and overall health score.
9
9
  */
@@ -15,10 +15,10 @@ const EventEmitter = require('../../utils/EventEmitter');
15
15
  * @constant {Object}
16
16
  */
17
17
  const HEALTH_STATUS = Object.freeze({
18
- GOOD: 'good',
19
- FAIR: 'fair',
20
- POOR: 'poor',
21
- UNKNOWN: 'unknown',
18
+ GOOD: 'good',
19
+ FAIR: 'fair',
20
+ POOR: 'poor',
21
+ UNKNOWN: 'unknown'
22
22
  });
23
23
 
24
24
  /**
@@ -26,26 +26,26 @@ const HEALTH_STATUS = Object.freeze({
26
26
  * @constant {Object}
27
27
  */
28
28
  const DEFAULT_CONFIG = Object.freeze({
29
- /** Sample window size for latency calculations */
30
- latencySampleSize: 100,
31
- /** Node timeout in ms (mark as inactive after this) */
32
- nodeTimeoutMs: 60 * 1000,
33
- /** Health check interval in ms */
34
- healthCheckIntervalMs: 30 * 1000,
35
- /** Latency thresholds in ms */
36
- latencyThresholds: {
37
- good: 500,
38
- fair: 1000,
39
- poor: 2000,
40
- },
41
- /** Packet loss thresholds (0-1) */
42
- packetLossThresholds: {
43
- good: 0.05,
44
- fair: 0.20,
45
- poor: 0.50,
46
- },
47
- /** Minimum nodes for good health */
48
- minActiveNodes: 2,
29
+ /** Sample window size for latency calculations */
30
+ latencySampleSize: 100,
31
+ /** Node timeout in ms (mark as inactive after this) */
32
+ nodeTimeoutMs: 60 * 1000,
33
+ /** Health check interval in ms */
34
+ healthCheckIntervalMs: 30 * 1000,
35
+ /** Latency thresholds in ms */
36
+ latencyThresholds: {
37
+ good: 500,
38
+ fair: 1000,
39
+ poor: 2000
40
+ },
41
+ /** Packet loss thresholds (0-1) */
42
+ packetLossThresholds: {
43
+ good: 0.05,
44
+ fair: 0.20,
45
+ poor: 0.50
46
+ },
47
+ /** Minimum nodes for good health */
48
+ minActiveNodes: 2
49
49
  });
50
50
 
51
51
  /**
@@ -73,416 +73,416 @@ const DEFAULT_CONFIG = Object.freeze({
73
73
 
74
74
  /**
75
75
  * Network health monitoring for mesh network.
76
- *
76
+ *
77
77
  * @class NetworkMonitor
78
78
  * @extends EventEmitter
79
79
  * @example
80
80
  * const monitor = new NetworkMonitor();
81
- *
81
+ *
82
82
  * // Track message delivery
83
83
  * monitor.trackMessageSent(peerId, messageId);
84
84
  * monitor.trackMessageDelivered(messageId, latencyMs);
85
- *
85
+ *
86
86
  * // Get health report
87
87
  * const health = monitor.generateHealthReport();
88
88
  * console.log(`${health.activeNodes} active peers, ${health.averageLatencyMs}ms latency`);
89
89
  */
90
90
  class NetworkMonitor extends EventEmitter {
91
- /**
91
+ /**
92
92
  * Creates a new NetworkMonitor instance.
93
93
  * @param {Object} [options={}] - Configuration options
94
94
  */
95
- constructor(options = {}) {
96
- super();
95
+ constructor(options = {}) {
96
+ super();
97
97
 
98
- /**
98
+ /**
99
99
  * Configuration
100
100
  * @type {Object}
101
101
  * @private
102
102
  */
103
- this._config = { ...DEFAULT_CONFIG, ...options };
103
+ this._config = { ...DEFAULT_CONFIG, ...options };
104
104
 
105
- /**
105
+ /**
106
106
  * Node health tracking
107
107
  * @type {Map<string, NodeHealth>}
108
108
  * @private
109
109
  */
110
- this._nodes = new Map();
110
+ this._nodes = new Map();
111
111
 
112
- /**
112
+ /**
113
113
  * Pending messages awaiting delivery confirmation
114
114
  * @type {Map<string, { peerId: string, timestamp: number }>}
115
115
  * @private
116
116
  */
117
- this._pendingMessages = new Map();
117
+ this._pendingMessages = new Map();
118
118
 
119
- /**
119
+ /**
120
120
  * Latency samples
121
121
  * @type {number[]}
122
122
  * @private
123
123
  */
124
- this._latencies = [];
124
+ this._latencies = [];
125
125
 
126
- /**
126
+ /**
127
127
  * Global statistics
128
128
  * @type {Object}
129
129
  * @private
130
130
  */
131
- this._stats = {
132
- totalMessagesSent: 0,
133
- totalMessagesDelivered: 0,
134
- totalMessagesFailed: 0,
135
- totalMessagesReceived: 0,
136
- };
137
-
138
- /**
131
+ this._stats = {
132
+ totalMessagesSent: 0,
133
+ totalMessagesDelivered: 0,
134
+ totalMessagesFailed: 0,
135
+ totalMessagesReceived: 0
136
+ };
137
+
138
+ /**
139
139
  * Health check timer
140
140
  * @type {number|null}
141
141
  * @private
142
142
  */
143
- this._healthCheckTimer = null;
143
+ this._healthCheckTimer = null;
144
144
 
145
- /**
145
+ /**
146
146
  * Last health report
147
147
  * @type {NetworkHealth|null}
148
148
  * @private
149
149
  */
150
- this._lastHealthReport = null;
150
+ this._lastHealthReport = null;
151
151
 
152
- // Start health check timer
153
- this._startHealthCheck();
154
- }
152
+ // Start health check timer
153
+ this._startHealthCheck();
154
+ }
155
155
 
156
- /**
156
+ /**
157
157
  * Tracks a message being sent to a peer.
158
158
  * @param {string} peerId - Target peer ID
159
159
  * @param {string} messageId - Message ID
160
160
  */
161
- trackMessageSent(peerId, messageId) {
162
- const now = Date.now();
161
+ trackMessageSent(peerId, messageId) {
162
+ const now = Date.now();
163
163
 
164
- // Update node stats
165
- const node = this._getOrCreateNode(peerId);
166
- node.messagesSent++;
167
- node.lastSeen = now;
164
+ // Update node stats
165
+ const node = this._getOrCreateNode(peerId);
166
+ node.messagesSent++;
167
+ node.lastSeen = now;
168
168
 
169
- // Track pending message
170
- this._pendingMessages.set(messageId, {
171
- peerId,
172
- timestamp: now,
173
- });
169
+ // Track pending message
170
+ this._pendingMessages.set(messageId, {
171
+ peerId,
172
+ timestamp: now
173
+ });
174
174
 
175
- this._stats.totalMessagesSent++;
176
- }
175
+ this._stats.totalMessagesSent++;
176
+ }
177
177
 
178
- /**
178
+ /**
179
179
  * Tracks successful message delivery.
180
180
  * @param {string} messageId - Message ID
181
181
  * @param {number} [latencyMs] - Delivery latency in ms
182
182
  */
183
- trackMessageDelivered(messageId, latencyMs) {
184
- const pending = this._pendingMessages.get(messageId);
185
- if (!pending) {
186
- return;
187
- }
183
+ trackMessageDelivered(messageId, latencyMs) {
184
+ const pending = this._pendingMessages.get(messageId);
185
+ if (!pending) {
186
+ return;
187
+ }
188
188
 
189
- const now = Date.now();
190
- const actualLatency = latencyMs || (now - pending.timestamp);
189
+ const now = Date.now();
190
+ const actualLatency = latencyMs || (now - pending.timestamp);
191
191
 
192
- // Update node stats
193
- const node = this._getOrCreateNode(pending.peerId);
194
- node.messagesDelivered = (node.messagesDelivered || 0) + 1;
195
- node.lastSeen = now;
192
+ // Update node stats
193
+ const node = this._getOrCreateNode(pending.peerId);
194
+ node.messagesDelivered = (node.messagesDelivered || 0) + 1;
195
+ node.lastSeen = now;
196
196
 
197
- // Update latency
198
- this._addLatencySample(actualLatency);
199
- this._updateNodeLatency(node, actualLatency);
197
+ // Update latency
198
+ this._addLatencySample(actualLatency);
199
+ this._updateNodeLatency(node, actualLatency);
200
200
 
201
- // Clean up
202
- this._pendingMessages.delete(messageId);
203
- this._stats.totalMessagesDelivered++;
204
- }
201
+ // Clean up
202
+ this._pendingMessages.delete(messageId);
203
+ this._stats.totalMessagesDelivered++;
204
+ }
205
205
 
206
- /**
206
+ /**
207
207
  * Tracks message delivery failure.
208
208
  * @param {string} messageId - Message ID
209
209
  */
210
- trackMessageFailed(messageId) {
211
- const pending = this._pendingMessages.get(messageId);
212
- if (!pending) {
213
- return;
214
- }
210
+ trackMessageFailed(messageId) {
211
+ const pending = this._pendingMessages.get(messageId);
212
+ if (!pending) {
213
+ return;
214
+ }
215
215
 
216
- // Update node stats
217
- const node = this._getOrCreateNode(pending.peerId);
218
- node.messagesFailed = (node.messagesFailed || 0) + 1;
216
+ // Update node stats
217
+ const node = this._getOrCreateNode(pending.peerId);
218
+ node.messagesFailed = (node.messagesFailed || 0) + 1;
219
219
 
220
- // Recalculate packet loss
221
- this._updateNodePacketLoss(node);
220
+ // Recalculate packet loss
221
+ this._updateNodePacketLoss(node);
222
222
 
223
- // Clean up
224
- this._pendingMessages.delete(messageId);
225
- this._stats.totalMessagesFailed++;
226
- }
223
+ // Clean up
224
+ this._pendingMessages.delete(messageId);
225
+ this._stats.totalMessagesFailed++;
226
+ }
227
227
 
228
- /**
228
+ /**
229
229
  * Tracks message received from a peer.
230
230
  * @param {string} peerId - Source peer ID
231
231
  */
232
- trackMessageReceived(peerId) {
233
- const node = this._getOrCreateNode(peerId);
234
- node.messagesReceived++;
235
- node.lastSeen = Date.now();
236
- this._stats.totalMessagesReceived++;
237
- }
238
-
239
- /**
232
+ trackMessageReceived(peerId) {
233
+ const node = this._getOrCreateNode(peerId);
234
+ node.messagesReceived++;
235
+ node.lastSeen = Date.now();
236
+ this._stats.totalMessagesReceived++;
237
+ }
238
+
239
+ /**
240
240
  * Registers a peer discovery/connection.
241
241
  * @param {string} peerId - Peer ID
242
242
  */
243
- trackPeerDiscovered(peerId) {
244
- const node = this._getOrCreateNode(peerId);
245
- node.discoveredAt = Date.now();
246
- node.lastSeen = Date.now();
247
- }
243
+ trackPeerDiscovered(peerId) {
244
+ const node = this._getOrCreateNode(peerId);
245
+ node.discoveredAt = Date.now();
246
+ node.lastSeen = Date.now();
247
+ }
248
248
 
249
- /**
249
+ /**
250
250
  * Tracks peer disconnection.
251
251
  * @param {string} peerId - Peer ID
252
252
  */
253
- trackPeerDisconnected(peerId) {
254
- const node = this._nodes.get(peerId);
255
- if (node) {
256
- node.isActive = false;
257
- node.disconnectedAt = Date.now();
258
- }
253
+ trackPeerDisconnected(peerId) {
254
+ const node = this._nodes.get(peerId);
255
+ if (node) {
256
+ node.isActive = false;
257
+ node.disconnectedAt = Date.now();
259
258
  }
259
+ }
260
260
 
261
- /**
261
+ /**
262
262
  * Generates a network health report.
263
263
  * @returns {NetworkHealth} Health report
264
264
  */
265
- generateHealthReport() {
266
- const now = Date.now();
267
- const timeout = this._config.nodeTimeoutMs;
268
-
269
- // Count active nodes
270
- let activeNodes = 0;
271
- let totalLatency = 0;
272
- let latencyCount = 0;
273
- let totalSent = 0;
274
- let totalDelivered = 0;
275
-
276
- for (const node of this._nodes.values()) {
277
- // Check if node is active
278
- const isActive = (now - node.lastSeen) < timeout;
279
- node.isActive = isActive;
280
-
281
- if (isActive) {
282
- activeNodes++;
283
-
284
- if (node.latency > 0) {
285
- totalLatency += node.latency;
286
- latencyCount++;
287
- }
288
-
289
- totalSent += node.messagesSent || 0;
290
- totalDelivered += (node.messagesDelivered || 0);
291
- }
265
+ generateHealthReport() {
266
+ const now = Date.now();
267
+ const timeout = this._config.nodeTimeoutMs;
268
+
269
+ // Count active nodes
270
+ let activeNodes = 0;
271
+ let totalLatency = 0;
272
+ let latencyCount = 0;
273
+ let totalSent = 0;
274
+ let totalDelivered = 0;
275
+
276
+ for (const node of this._nodes.values()) {
277
+ // Check if node is active
278
+ const isActive = (now - node.lastSeen) < timeout;
279
+ node.isActive = isActive;
280
+
281
+ if (isActive) {
282
+ activeNodes++;
283
+
284
+ if (node.latency > 0) {
285
+ totalLatency += node.latency;
286
+ latencyCount++;
292
287
  }
293
288
 
294
- // Calculate averages
295
- const averageLatency = latencyCount > 0
296
- ? Math.round(totalLatency / latencyCount)
297
- : this._calculateAverageLatency();
298
-
299
- const packetLoss = totalSent > 0
300
- ? 1 - (totalDelivered / totalSent)
301
- : 0;
302
-
303
- // Assess overall health
304
- const health = this._assessHealth(activeNodes, averageLatency, packetLoss);
305
-
306
- const report = {
307
- activeNodes,
308
- totalKnownNodes: this._nodes.size,
309
- averageLatencyMs: averageLatency,
310
- packetLossRate: Math.round(packetLoss * 1000) / 1000,
311
- overallHealth: health,
312
- lastUpdated: now,
313
- };
314
-
315
- this._lastHealthReport = report;
316
-
317
- // Emit event if health changed
318
- if (this._previousHealth !== health) {
319
- this.emit('health-changed', {
320
- previous: this._previousHealth || HEALTH_STATUS.UNKNOWN,
321
- current: health,
322
- report,
323
- });
324
- this._previousHealth = health;
325
- }
289
+ totalSent += node.messagesSent || 0;
290
+ totalDelivered += (node.messagesDelivered || 0);
291
+ }
292
+ }
326
293
 
327
- return report;
294
+ // Calculate averages
295
+ const averageLatency = latencyCount > 0
296
+ ? Math.round(totalLatency / latencyCount)
297
+ : this._calculateAverageLatency();
298
+
299
+ const packetLoss = totalSent > 0
300
+ ? 1 - (totalDelivered / totalSent)
301
+ : 0;
302
+
303
+ // Assess overall health
304
+ const health = this._assessHealth(activeNodes, averageLatency, packetLoss);
305
+
306
+ const report = {
307
+ activeNodes,
308
+ totalKnownNodes: this._nodes.size,
309
+ averageLatencyMs: averageLatency,
310
+ packetLossRate: Math.round(packetLoss * 1000) / 1000,
311
+ overallHealth: health,
312
+ lastUpdated: now
313
+ };
314
+
315
+ this._lastHealthReport = report;
316
+
317
+ // Emit event if health changed
318
+ if (this._previousHealth !== health) {
319
+ this.emit('health-changed', {
320
+ previous: this._previousHealth || HEALTH_STATUS.UNKNOWN,
321
+ current: health,
322
+ report
323
+ });
324
+ this._previousHealth = health;
328
325
  }
329
326
 
330
- /**
327
+ return report;
328
+ }
329
+
330
+ /**
331
331
  * Gets detailed health for a specific node.
332
332
  * @param {string} peerId - Peer ID
333
333
  * @returns {NodeHealth|null} Node health or null
334
334
  */
335
- getNodeHealth(peerId) {
336
- const node = this._nodes.get(peerId);
337
- if (!node) {
338
- return null;
339
- }
340
-
341
- const now = Date.now();
342
- return {
343
- ...node,
344
- isActive: (now - node.lastSeen) < this._config.nodeTimeoutMs,
345
- };
335
+ getNodeHealth(peerId) {
336
+ const node = this._nodes.get(peerId);
337
+ if (!node) {
338
+ return null;
346
339
  }
347
340
 
348
- /**
341
+ const now = Date.now();
342
+ return {
343
+ ...node,
344
+ isActive: (now - node.lastSeen) < this._config.nodeTimeoutMs
345
+ };
346
+ }
347
+
348
+ /**
349
349
  * Gets health information for all nodes.
350
350
  * @returns {NodeHealth[]} Array of node health
351
351
  */
352
- getAllNodeHealth() {
353
- const now = Date.now();
354
- const timeout = this._config.nodeTimeoutMs;
355
-
356
- return Array.from(this._nodes.values()).map(node => ({
357
- ...node,
358
- isActive: (now - node.lastSeen) < timeout,
359
- }));
360
- }
352
+ getAllNodeHealth() {
353
+ const now = Date.now();
354
+ const timeout = this._config.nodeTimeoutMs;
361
355
 
362
- /**
356
+ return Array.from(this._nodes.values()).map(node => ({
357
+ ...node,
358
+ isActive: (now - node.lastSeen) < timeout
359
+ }));
360
+ }
361
+
362
+ /**
363
363
  * Gets the last generated health report.
364
364
  * @returns {NetworkHealth|null} Last report
365
365
  */
366
- getLastHealthReport() {
367
- return this._lastHealthReport;
368
- }
366
+ getLastHealthReport() {
367
+ return this._lastHealthReport;
368
+ }
369
369
 
370
- /**
370
+ /**
371
371
  * Gets monitoring statistics.
372
372
  * @returns {Object} Statistics
373
373
  */
374
- getStats() {
375
- return {
376
- ...this._stats,
377
- knownNodes: this._nodes.size,
378
- pendingMessages: this._pendingMessages.size,
379
- averageLatency: this._calculateAverageLatency(),
380
- };
381
- }
382
-
383
- /**
374
+ getStats() {
375
+ return {
376
+ ...this._stats,
377
+ knownNodes: this._nodes.size,
378
+ pendingMessages: this._pendingMessages.size,
379
+ averageLatency: this._calculateAverageLatency()
380
+ };
381
+ }
382
+
383
+ /**
384
384
  * Resets all monitoring data.
385
385
  */
386
- reset() {
387
- this._nodes.clear();
388
- this._pendingMessages.clear();
389
- this._latencies = [];
390
- this._stats = {
391
- totalMessagesSent: 0,
392
- totalMessagesDelivered: 0,
393
- totalMessagesFailed: 0,
394
- totalMessagesReceived: 0,
395
- };
396
- this._lastHealthReport = null;
397
- this._previousHealth = null;
398
- }
399
-
400
- /**
386
+ reset() {
387
+ this._nodes.clear();
388
+ this._pendingMessages.clear();
389
+ this._latencies = [];
390
+ this._stats = {
391
+ totalMessagesSent: 0,
392
+ totalMessagesDelivered: 0,
393
+ totalMessagesFailed: 0,
394
+ totalMessagesReceived: 0
395
+ };
396
+ this._lastHealthReport = null;
397
+ this._previousHealth = null;
398
+ }
399
+
400
+ /**
401
401
  * Destroys the monitor.
402
402
  */
403
- destroy() {
404
- this._stopHealthCheck();
405
- this.reset();
406
- this.removeAllListeners();
407
- }
403
+ destroy() {
404
+ this._stopHealthCheck();
405
+ this.reset();
406
+ this.removeAllListeners();
407
+ }
408
408
 
409
- /**
409
+ /**
410
410
  * Gets or creates a node entry.
411
411
  * @param {string} peerId - Peer ID
412
412
  * @returns {NodeHealth} Node entry
413
413
  * @private
414
414
  */
415
- _getOrCreateNode(peerId) {
416
- if (!this._nodes.has(peerId)) {
417
- this._nodes.set(peerId, {
418
- peerId,
419
- lastSeen: Date.now(),
420
- latency: 0,
421
- messagesReceived: 0,
422
- messagesSent: 0,
423
- messagesDelivered: 0,
424
- messagesFailed: 0,
425
- packetLoss: 0,
426
- isActive: true,
427
- });
428
- }
429
- return this._nodes.get(peerId);
415
+ _getOrCreateNode(peerId) {
416
+ if (!this._nodes.has(peerId)) {
417
+ this._nodes.set(peerId, {
418
+ peerId,
419
+ lastSeen: Date.now(),
420
+ latency: 0,
421
+ messagesReceived: 0,
422
+ messagesSent: 0,
423
+ messagesDelivered: 0,
424
+ messagesFailed: 0,
425
+ packetLoss: 0,
426
+ isActive: true
427
+ });
430
428
  }
429
+ return this._nodes.get(peerId);
430
+ }
431
431
 
432
- /**
432
+ /**
433
433
  * Adds a latency sample.
434
434
  * @param {number} latency - Latency in ms
435
435
  * @private
436
436
  */
437
- _addLatencySample(latency) {
438
- this._latencies.push(latency);
439
- if (this._latencies.length > this._config.latencySampleSize) {
440
- this._latencies.shift();
441
- }
437
+ _addLatencySample(latency) {
438
+ this._latencies.push(latency);
439
+ if (this._latencies.length > this._config.latencySampleSize) {
440
+ this._latencies.shift();
442
441
  }
442
+ }
443
443
 
444
- /**
444
+ /**
445
445
  * Updates node latency with exponential moving average.
446
446
  * @param {NodeHealth} node - Node to update
447
447
  * @param {number} latency - New latency sample
448
448
  * @private
449
449
  */
450
- _updateNodeLatency(node, latency) {
451
- const alpha = 0.2; // Smoothing factor
452
- if (node.latency === 0) {
453
- node.latency = latency;
454
- } else {
455
- node.latency = alpha * latency + (1 - alpha) * node.latency;
456
- }
450
+ _updateNodeLatency(node, latency) {
451
+ const alpha = 0.2; // Smoothing factor
452
+ if (node.latency === 0) {
453
+ node.latency = latency;
454
+ } else {
455
+ node.latency = alpha * latency + (1 - alpha) * node.latency;
457
456
  }
457
+ }
458
458
 
459
- /**
459
+ /**
460
460
  * Updates node packet loss rate.
461
461
  * @param {NodeHealth} node - Node to update
462
462
  * @private
463
463
  */
464
- _updateNodePacketLoss(node) {
465
- const total = (node.messagesDelivered || 0) + (node.messagesFailed || 0);
466
- if (total > 0) {
467
- node.packetLoss = (node.messagesFailed || 0) / total;
468
- }
464
+ _updateNodePacketLoss(node) {
465
+ const total = (node.messagesDelivered || 0) + (node.messagesFailed || 0);
466
+ if (total > 0) {
467
+ node.packetLoss = (node.messagesFailed || 0) / total;
469
468
  }
469
+ }
470
470
 
471
- /**
471
+ /**
472
472
  * Calculates average latency from samples.
473
473
  * @returns {number} Average latency
474
474
  * @private
475
475
  */
476
- _calculateAverageLatency() {
477
- if (this._latencies.length === 0) {
478
- return 0;
479
- }
480
- return Math.round(
481
- this._latencies.reduce((a, b) => a + b, 0) / this._latencies.length
482
- );
476
+ _calculateAverageLatency() {
477
+ if (this._latencies.length === 0) {
478
+ return 0;
483
479
  }
480
+ return Math.round(
481
+ this._latencies.reduce((a, b) => a + b, 0) / this._latencies.length
482
+ );
483
+ }
484
484
 
485
- /**
485
+ /**
486
486
  * Assesses overall network health.
487
487
  * @param {number} activeNodes - Active node count
488
488
  * @param {number} latency - Average latency
@@ -490,54 +490,65 @@ class NetworkMonitor extends EventEmitter {
490
490
  * @returns {string} Health status
491
491
  * @private
492
492
  */
493
- _assessHealth(activeNodes, latency, packetLoss) {
494
- const { latencyThresholds, packetLossThresholds, minActiveNodes } = this._config;
493
+ _assessHealth(activeNodes, latency, packetLoss) {
494
+ const { latencyThresholds, packetLossThresholds, minActiveNodes } = this._config;
495
495
 
496
- // Check for poor indicators
497
- if (activeNodes < minActiveNodes ||
496
+ // Check for poor indicators
497
+ if (activeNodes < minActiveNodes ||
498
498
  packetLoss > packetLossThresholds.poor ||
499
499
  latency > latencyThresholds.poor) {
500
- return HEALTH_STATUS.POOR;
501
- }
500
+ return HEALTH_STATUS.POOR;
501
+ }
502
502
 
503
- // Check for fair indicators
504
- if (activeNodes < minActiveNodes * 2 ||
503
+ // Check for fair indicators
504
+ if (activeNodes < minActiveNodes * 2 ||
505
505
  packetLoss > packetLossThresholds.fair ||
506
506
  latency > latencyThresholds.fair) {
507
- return HEALTH_STATUS.FAIR;
508
- }
509
-
510
- return HEALTH_STATUS.GOOD;
507
+ return HEALTH_STATUS.FAIR;
511
508
  }
512
509
 
513
- /**
510
+ return HEALTH_STATUS.GOOD;
511
+ }
512
+
513
+ /**
514
514
  * Starts periodic health checks.
515
515
  * @private
516
516
  */
517
- _startHealthCheck() {
518
- this._healthCheckTimer = setInterval(
519
- () => {
520
- const report = this.generateHealthReport();
521
- this.emit('health-report', report);
522
- },
523
- this._config.healthCheckIntervalMs
524
- );
517
+ _startHealthCheck() {
518
+ if (this._healthCheckTimer) { return; }
519
+
520
+ this._healthCheckTimer = setInterval(
521
+ () => {
522
+ try {
523
+ const report = this.generateHealthReport();
524
+ this.emit('health-report', report);
525
+ } catch (error) {
526
+ // Don't let health check errors crash the monitor
527
+ }
528
+ },
529
+ this._config.healthCheckIntervalMs
530
+ );
531
+
532
+ // Don't prevent process exit
533
+ if (this._healthCheckTimer && typeof this._healthCheckTimer.unref === 'function') {
534
+ this._healthCheckTimer.unref();
525
535
  }
536
+ }
526
537
 
527
- /**
538
+ /**
528
539
  * Stops periodic health checks.
529
540
  * @private
530
541
  */
531
- _stopHealthCheck() {
532
- if (this._healthCheckTimer) {
533
- clearInterval(this._healthCheckTimer);
534
- this._healthCheckTimer = null;
535
- }
542
+ _stopHealthCheck() {
543
+ if (this._healthCheckTimer) {
544
+ clearInterval(this._healthCheckTimer);
545
+ this._healthCheckTimer = null;
536
546
  }
547
+ }
537
548
  }
538
549
 
539
550
  module.exports = {
540
- NetworkMonitor,
541
- HEALTH_STATUS,
542
- DEFAULT_CONFIG,
551
+ NetworkMonitor,
552
+ HEALTH_STATUS,
553
+ DEFAULT_CONFIG
543
554
  };