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.
- package/README.md +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +70 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +659 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +79 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
- package/src/crypto/providers/QuickCryptoProvider.js +134 -0
- package/src/crypto/providers/TweetNaClProvider.js +124 -0
- package/src/crypto/providers/index.js +11 -0
- package/src/errors/MeshError.js +2 -1
- package/src/expo/withBLEMesh.js +102 -0
- package/src/hooks/useMesh.js +30 -9
- package/src/hooks/useMessages.js +2 -0
- package/src/index.js +23 -8
- package/src/mesh/dedup/DedupManager.js +36 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +408 -0
- package/src/mesh/monitor/NetworkMonitor.js +327 -316
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/PeerManager.js +6 -1
- package/src/mesh/router/MessageRouter.js +26 -15
- package/src/mesh/router/RouteTable.js +7 -1
- package/src/mesh/store/StoreAndForwardManager.js +295 -297
- package/src/mesh/store/index.js +1 -1
- package/src/service/BatteryOptimizer.js +282 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +167 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +77 -2
- package/src/service/audio/AudioManager.js +8 -2
- package/src/service/file/FileAssembler.js +106 -0
- package/src/service/file/FileChunker.js +79 -0
- package/src/service/file/FileManager.js +307 -0
- package/src/service/file/FileMessage.js +122 -0
- package/src/service/file/index.js +15 -0
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/transport/BLETransport.js +131 -9
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +305 -0
- package/src/transport/WiFiDirectTransport.js +295 -0
- package/src/transport/adapters/NodeBLEAdapter.js +34 -0
- package/src/transport/adapters/RNBLEAdapter.js +56 -1
- package/src/transport/index.js +6 -0
- package/src/utils/compression.js +291 -291
- package/src/crypto/aead.js +0 -189
- package/src/crypto/chacha20.js +0 -181
- package/src/crypto/hkdf.js +0 -187
- package/src/crypto/hmac.js +0 -143
- package/src/crypto/keys/KeyManager.js +0 -271
- package/src/crypto/keys/KeyPair.js +0 -216
- package/src/crypto/keys/SecureStorage.js +0 -219
- package/src/crypto/keys/index.js +0 -32
- package/src/crypto/noise/handshake.js +0 -410
- package/src/crypto/noise/index.js +0 -27
- package/src/crypto/noise/session.js +0 -253
- package/src/crypto/noise/state.js +0 -268
- package/src/crypto/poly1305.js +0 -113
- package/src/crypto/sha256.js +0 -240
- 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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
96
|
-
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
super();
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
/**
|
|
99
99
|
* Configuration
|
|
100
100
|
* @type {Object}
|
|
101
101
|
* @private
|
|
102
102
|
*/
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
this._pendingMessages = new Map();
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
120
|
* Latency samples
|
|
121
121
|
* @type {number[]}
|
|
122
122
|
* @private
|
|
123
123
|
*/
|
|
124
|
-
|
|
124
|
+
this._latencies = [];
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
/**
|
|
127
127
|
* Global statistics
|
|
128
128
|
* @type {Object}
|
|
129
129
|
* @private
|
|
130
130
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
143
|
+
this._healthCheckTimer = null;
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
146
|
* Last health report
|
|
147
147
|
* @type {NetworkHealth|null}
|
|
148
148
|
* @private
|
|
149
149
|
*/
|
|
150
|
-
|
|
150
|
+
this._lastHealthReport = null;
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
162
|
-
|
|
161
|
+
trackMessageSent(peerId, messageId) {
|
|
162
|
+
const now = Date.now();
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// Update node stats
|
|
165
|
+
const node = this._getOrCreateNode(peerId);
|
|
166
|
+
node.messagesSent++;
|
|
167
|
+
node.lastSeen = now;
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
// Track pending message
|
|
170
|
+
this._pendingMessages.set(messageId, {
|
|
171
|
+
peerId,
|
|
172
|
+
timestamp: now
|
|
173
|
+
});
|
|
174
174
|
|
|
175
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
trackMessageDelivered(messageId, latencyMs) {
|
|
184
|
+
const pending = this._pendingMessages.get(messageId);
|
|
185
|
+
if (!pending) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const actualLatency = latencyMs || (now - pending.timestamp);
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
// Update latency
|
|
198
|
+
this._addLatencySample(actualLatency);
|
|
199
|
+
this._updateNodeLatency(node, actualLatency);
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
trackMessageFailed(messageId) {
|
|
211
|
+
const pending = this._pendingMessages.get(messageId);
|
|
212
|
+
if (!pending) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
// Update node stats
|
|
217
|
+
const node = this._getOrCreateNode(pending.peerId);
|
|
218
|
+
node.messagesFailed = (node.messagesFailed || 0) + 1;
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
// Recalculate packet loss
|
|
221
|
+
this._updateNodePacketLoss(node);
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
494
|
-
|
|
493
|
+
_assessHealth(activeNodes, latency, packetLoss) {
|
|
494
|
+
const { latencyThresholds, packetLossThresholds, minActiveNodes } = this._config;
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
// Check for poor indicators
|
|
497
|
+
if (activeNodes < minActiveNodes ||
|
|
498
498
|
packetLoss > packetLossThresholds.poor ||
|
|
499
499
|
latency > latencyThresholds.poor) {
|
|
500
|
-
|
|
501
|
-
|
|
500
|
+
return HEALTH_STATUS.POOR;
|
|
501
|
+
}
|
|
502
502
|
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
// Check for fair indicators
|
|
504
|
+
if (activeNodes < minActiveNodes * 2 ||
|
|
505
505
|
packetLoss > packetLossThresholds.fair ||
|
|
506
506
|
latency > latencyThresholds.fair) {
|
|
507
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
551
|
+
NetworkMonitor,
|
|
552
|
+
HEALTH_STATUS,
|
|
553
|
+
DEFAULT_CONFIG
|
|
543
554
|
};
|