react-native-ble-mesh 2.0.0 → 2.1.2
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 +2 -2
- package/docs/OPTIMIZATION.md +165 -52
- package/package.json +1 -1
- package/src/MeshNetwork.js +63 -53
- package/src/constants/audio.js +4 -4
- package/src/constants/ble.js +1 -1
- package/src/constants/crypto.js +1 -1
- package/src/constants/errors.js +2 -2
- package/src/constants/events.js +1 -1
- package/src/constants/protocol.js +2 -2
- package/src/crypto/AutoCrypto.js +16 -3
- package/src/crypto/CryptoProvider.js +17 -17
- package/src/crypto/providers/ExpoCryptoProvider.js +15 -9
- package/src/crypto/providers/QuickCryptoProvider.js +41 -12
- package/src/crypto/providers/TweetNaClProvider.js +10 -8
- package/src/errors/AudioError.js +2 -1
- package/src/errors/ConnectionError.js +2 -2
- package/src/errors/CryptoError.js +1 -1
- package/src/errors/HandshakeError.js +2 -2
- package/src/errors/MeshError.js +4 -4
- package/src/errors/MessageError.js +2 -2
- package/src/errors/ValidationError.js +3 -3
- package/src/expo/withBLEMesh.js +10 -10
- package/src/hooks/AppStateManager.js +11 -2
- package/src/hooks/useMesh.js +23 -10
- package/src/hooks/useMessages.js +17 -16
- package/src/hooks/usePeers.js +19 -14
- package/src/index.js +2 -2
- package/src/mesh/dedup/BloomFilter.js +45 -57
- package/src/mesh/dedup/DedupManager.js +36 -8
- package/src/mesh/dedup/MessageCache.js +3 -0
- package/src/mesh/fragment/Assembler.js +5 -4
- package/src/mesh/fragment/Fragmenter.js +3 -3
- package/src/mesh/monitor/ConnectionQuality.js +59 -25
- package/src/mesh/monitor/NetworkMonitor.js +80 -28
- package/src/mesh/peer/Peer.js +9 -11
- package/src/mesh/peer/PeerDiscovery.js +18 -19
- package/src/mesh/peer/PeerManager.js +29 -17
- package/src/mesh/router/MessageRouter.js +28 -20
- package/src/mesh/router/PathFinder.js +10 -13
- package/src/mesh/router/RouteTable.js +25 -14
- package/src/mesh/store/StoreAndForwardManager.js +32 -24
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +18 -14
- package/src/protocol/serializer.js +9 -12
- package/src/protocol/validator.js +29 -10
- package/src/service/BatteryOptimizer.js +22 -18
- package/src/service/EmergencyManager.js +18 -25
- package/src/service/HandshakeManager.js +112 -18
- package/src/service/MeshService.js +106 -22
- package/src/service/SessionManager.js +50 -13
- package/src/service/audio/AudioManager.js +80 -38
- package/src/service/audio/buffer/FrameBuffer.js +7 -8
- package/src/service/audio/buffer/JitterBuffer.js +1 -1
- package/src/service/audio/codec/LC3Codec.js +18 -19
- package/src/service/audio/codec/LC3Decoder.js +10 -10
- package/src/service/audio/codec/LC3Encoder.js +11 -9
- package/src/service/audio/session/AudioSession.js +14 -17
- package/src/service/audio/session/VoiceMessage.js +15 -22
- package/src/service/audio/transport/AudioFragmenter.js +17 -9
- package/src/service/audio/transport/AudioFramer.js +8 -12
- package/src/service/file/FileAssembler.js +4 -2
- package/src/service/file/FileChunker.js +1 -1
- package/src/service/file/FileManager.js +26 -20
- package/src/service/file/FileMessage.js +7 -12
- package/src/service/text/TextManager.js +75 -42
- package/src/service/text/broadcast/BroadcastManager.js +14 -17
- package/src/service/text/channel/Channel.js +10 -14
- package/src/service/text/channel/ChannelManager.js +10 -10
- package/src/service/text/message/TextMessage.js +12 -19
- package/src/service/text/message/TextSerializer.js +2 -2
- package/src/storage/AsyncStorageAdapter.js +17 -14
- package/src/storage/MemoryStorage.js +11 -8
- package/src/storage/MessageStore.js +77 -32
- package/src/storage/Storage.js +9 -9
- package/src/transport/BLETransport.js +27 -16
- package/src/transport/MockTransport.js +7 -2
- package/src/transport/MultiTransport.js +43 -11
- package/src/transport/Transport.js +9 -9
- package/src/transport/WiFiDirectTransport.js +26 -20
- package/src/transport/adapters/BLEAdapter.js +19 -19
- package/src/transport/adapters/NodeBLEAdapter.js +24 -23
- package/src/transport/adapters/RNBLEAdapter.js +14 -11
- package/src/utils/EventEmitter.js +15 -16
- package/src/utils/LRUCache.js +10 -4
- package/src/utils/RateLimiter.js +1 -1
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +10 -8
- package/src/utils/encoding.js +39 -8
- package/src/utils/retry.js +11 -13
- package/src/utils/time.js +9 -4
- package/src/utils/validation.js +1 -1
|
@@ -66,7 +66,7 @@ function createFragmentHeader(index, total, payloadLength) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Parses a fragment header
|
|
68
68
|
* @param {Uint8Array} data - Fragment data
|
|
69
|
-
* @returns {
|
|
69
|
+
* @returns {any} Parsed header { index, total, payloadLength }
|
|
70
70
|
*/
|
|
71
71
|
function parseFragmentHeader(data) {
|
|
72
72
|
if (data.length < FRAGMENT_HEADER_SIZE) {
|
|
@@ -121,7 +121,7 @@ function fragment(payload, messageId, maxSize = DEFAULT_FRAGMENT_SIZE) {
|
|
|
121
121
|
for (let i = 0; i < fragmentCount; i++) {
|
|
122
122
|
const remainingLength = payload.length - offset;
|
|
123
123
|
const chunkLength = Math.min(payloadCapacity, remainingLength);
|
|
124
|
-
const chunk = payload.
|
|
124
|
+
const chunk = payload.subarray(offset, offset + chunkLength);
|
|
125
125
|
|
|
126
126
|
// Create fragment with header
|
|
127
127
|
const header = createFragmentHeader(i, fragmentCount, chunkLength);
|
|
@@ -139,7 +139,7 @@ function fragment(payload, messageId, maxSize = DEFAULT_FRAGMENT_SIZE) {
|
|
|
139
139
|
/**
|
|
140
140
|
* Gets fragment information without parsing full data
|
|
141
141
|
* @param {Uint8Array} fragmentData - Fragment data
|
|
142
|
-
* @returns {
|
|
142
|
+
* @returns {any} Fragment info { index, total, payloadLength, payload }
|
|
143
143
|
*/
|
|
144
144
|
function getFragmentInfo(fragmentData) {
|
|
145
145
|
const header = parseFragmentHeader(fragmentData);
|
|
@@ -12,7 +12,7 @@ const EventEmitter = require('../../utils/EventEmitter');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Connection quality levels
|
|
15
|
-
* @constant {
|
|
15
|
+
* @constant {any}
|
|
16
16
|
*/
|
|
17
17
|
const QUALITY_LEVEL = Object.freeze({
|
|
18
18
|
EXCELLENT: 'excellent',
|
|
@@ -24,7 +24,7 @@ const QUALITY_LEVEL = Object.freeze({
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Default quality configuration
|
|
27
|
-
* @constant {
|
|
27
|
+
* @constant {any}
|
|
28
28
|
*/
|
|
29
29
|
const DEFAULT_CONFIG = Object.freeze({
|
|
30
30
|
/** How often to recalculate quality (ms) */
|
|
@@ -50,14 +50,28 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
50
50
|
* @class PeerQualityTracker
|
|
51
51
|
*/
|
|
52
52
|
class PeerQualityTracker {
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} peerId
|
|
55
|
+
* @param {any} config
|
|
56
|
+
*/
|
|
53
57
|
constructor(peerId, config) {
|
|
54
58
|
this.peerId = peerId;
|
|
59
|
+
/** @type {any} */
|
|
55
60
|
this._config = config;
|
|
56
|
-
|
|
57
|
-
this.
|
|
61
|
+
// Circular buffer for RSSI samples
|
|
62
|
+
this._rssiSamples = new Float64Array(config.sampleSize);
|
|
63
|
+
this._rssiIndex = 0;
|
|
64
|
+
this._rssiCount = 0;
|
|
65
|
+
this._rssiSum = 0;
|
|
66
|
+
// Circular buffer for latency samples
|
|
67
|
+
this._latencySamples = new Float64Array(config.sampleSize);
|
|
68
|
+
this._latencyIndex = 0;
|
|
69
|
+
this._latencyCount = 0;
|
|
70
|
+
this._latencySum = 0;
|
|
58
71
|
this._packetsSent = 0;
|
|
59
72
|
this._packetsAcked = 0;
|
|
60
73
|
this._bytesTransferred = 0;
|
|
74
|
+
/** @type {number|null} */
|
|
61
75
|
this._transferStartTime = null;
|
|
62
76
|
this._lastActivity = Date.now();
|
|
63
77
|
this._transport = 'ble';
|
|
@@ -65,25 +79,37 @@ class PeerQualityTracker {
|
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
/**
|
|
68
|
-
* Records an RSSI sample
|
|
82
|
+
* Records an RSSI sample using circular buffer
|
|
69
83
|
* @param {number} rssi - Signal strength in dBm
|
|
70
84
|
*/
|
|
71
85
|
recordRssi(rssi) {
|
|
72
|
-
this._rssiSamples.
|
|
73
|
-
if (this.
|
|
74
|
-
this._rssiSamples.
|
|
86
|
+
const capacity = this._rssiSamples.length;
|
|
87
|
+
if (this._rssiCount >= capacity) {
|
|
88
|
+
this._rssiSum -= this._rssiSamples[this._rssiIndex];
|
|
89
|
+
}
|
|
90
|
+
this._rssiSamples[this._rssiIndex] = rssi;
|
|
91
|
+
this._rssiSum += rssi;
|
|
92
|
+
this._rssiIndex = (this._rssiIndex + 1) % capacity;
|
|
93
|
+
if (this._rssiCount < capacity) {
|
|
94
|
+
this._rssiCount++;
|
|
75
95
|
}
|
|
76
96
|
this._lastActivity = Date.now();
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
/**
|
|
80
|
-
* Records a latency measurement
|
|
100
|
+
* Records a latency measurement using circular buffer
|
|
81
101
|
* @param {number} latencyMs - Round-trip latency in ms
|
|
82
102
|
*/
|
|
83
103
|
recordLatency(latencyMs) {
|
|
84
|
-
this._latencySamples.
|
|
85
|
-
if (this.
|
|
86
|
-
this._latencySamples.
|
|
104
|
+
const capacity = this._latencySamples.length;
|
|
105
|
+
if (this._latencyCount >= capacity) {
|
|
106
|
+
this._latencySum -= this._latencySamples[this._latencyIndex];
|
|
107
|
+
}
|
|
108
|
+
this._latencySamples[this._latencyIndex] = latencyMs;
|
|
109
|
+
this._latencySum += latencyMs;
|
|
110
|
+
this._latencyIndex = (this._latencyIndex + 1) % capacity;
|
|
111
|
+
if (this._latencyCount < capacity) {
|
|
112
|
+
this._latencyCount++;
|
|
87
113
|
}
|
|
88
114
|
this._lastActivity = Date.now();
|
|
89
115
|
}
|
|
@@ -125,21 +151,21 @@ class PeerQualityTracker {
|
|
|
125
151
|
}
|
|
126
152
|
|
|
127
153
|
/**
|
|
128
|
-
* Gets the average RSSI
|
|
154
|
+
* Gets the average RSSI (O(1) using running sum)
|
|
129
155
|
* @returns {number|null}
|
|
130
156
|
*/
|
|
131
157
|
getAvgRssi() {
|
|
132
|
-
if (this.
|
|
133
|
-
return this.
|
|
158
|
+
if (this._rssiCount === 0) { return null; }
|
|
159
|
+
return this._rssiSum / this._rssiCount;
|
|
134
160
|
}
|
|
135
161
|
|
|
136
162
|
/**
|
|
137
|
-
* Gets the average latency
|
|
163
|
+
* Gets the average latency (O(1) using running sum)
|
|
138
164
|
* @returns {number|null}
|
|
139
165
|
*/
|
|
140
166
|
getAvgLatency() {
|
|
141
|
-
if (this.
|
|
142
|
-
return this.
|
|
167
|
+
if (this._latencyCount === 0) { return null; }
|
|
168
|
+
return this._latencySum / this._latencyCount;
|
|
143
169
|
}
|
|
144
170
|
|
|
145
171
|
/**
|
|
@@ -173,7 +199,7 @@ class PeerQualityTracker {
|
|
|
173
199
|
/**
|
|
174
200
|
* Calculates quality score (0-1) for a metric
|
|
175
201
|
* @param {number|null} value - Metric value
|
|
176
|
-
* @param {
|
|
202
|
+
* @param {any} thresholds - {excellent, good, fair}
|
|
177
203
|
* @param {boolean} [higherIsBetter=false]
|
|
178
204
|
* @returns {number}
|
|
179
205
|
*/
|
|
@@ -196,7 +222,7 @@ class PeerQualityTracker {
|
|
|
196
222
|
|
|
197
223
|
/**
|
|
198
224
|
* Calculates overall quality
|
|
199
|
-
* @returns {
|
|
225
|
+
* @returns {any} Quality report
|
|
200
226
|
*/
|
|
201
227
|
calculate() {
|
|
202
228
|
if (this.isTimedOut()) {
|
|
@@ -231,6 +257,7 @@ class PeerQualityTracker {
|
|
|
231
257
|
if (score >= 0.8) { level = QUALITY_LEVEL.EXCELLENT; } else if (score >= 0.6) { level = QUALITY_LEVEL.GOOD; } else if (score >= 0.4) { level = QUALITY_LEVEL.FAIR; } else { level = QUALITY_LEVEL.POOR; }
|
|
232
258
|
|
|
233
259
|
const previousLevel = this._currentLevel;
|
|
260
|
+
// @ts-ignore
|
|
234
261
|
this._currentLevel = level;
|
|
235
262
|
|
|
236
263
|
return {
|
|
@@ -255,13 +282,15 @@ class PeerQualityTracker {
|
|
|
255
282
|
*/
|
|
256
283
|
class ConnectionQuality extends EventEmitter {
|
|
257
284
|
/**
|
|
258
|
-
* @param {
|
|
285
|
+
* @param {any} [config={}]
|
|
259
286
|
*/
|
|
260
287
|
constructor(config = {}) {
|
|
261
288
|
super();
|
|
289
|
+
/** @type {any} */
|
|
262
290
|
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
263
291
|
/** @type {Map<string, PeerQualityTracker>} */
|
|
264
292
|
this._peers = new Map();
|
|
293
|
+
/** @type {any} */
|
|
265
294
|
this._updateTimer = null;
|
|
266
295
|
}
|
|
267
296
|
|
|
@@ -301,7 +330,7 @@ class ConnectionQuality extends EventEmitter {
|
|
|
301
330
|
if (!this._peers.has(peerId)) {
|
|
302
331
|
this._peers.set(peerId, new PeerQualityTracker(peerId, this._config));
|
|
303
332
|
}
|
|
304
|
-
return this._peers.get(peerId);
|
|
333
|
+
return /** @type {PeerQualityTracker} */ (this._peers.get(peerId));
|
|
305
334
|
}
|
|
306
335
|
|
|
307
336
|
/**
|
|
@@ -367,7 +396,7 @@ class ConnectionQuality extends EventEmitter {
|
|
|
367
396
|
/**
|
|
368
397
|
* Gets quality for a specific peer
|
|
369
398
|
* @param {string} peerId
|
|
370
|
-
* @returns {
|
|
399
|
+
* @returns {any}
|
|
371
400
|
*/
|
|
372
401
|
getQuality(peerId) {
|
|
373
402
|
const tracker = this._peers.get(peerId);
|
|
@@ -377,7 +406,7 @@ class ConnectionQuality extends EventEmitter {
|
|
|
377
406
|
|
|
378
407
|
/**
|
|
379
408
|
* Gets quality for all peers
|
|
380
|
-
* @returns {
|
|
409
|
+
* @returns {any[]}
|
|
381
410
|
*/
|
|
382
411
|
getAllQuality() {
|
|
383
412
|
const results = [];
|
|
@@ -388,10 +417,15 @@ class ConnectionQuality extends EventEmitter {
|
|
|
388
417
|
}
|
|
389
418
|
|
|
390
419
|
/**
|
|
391
|
-
* Periodic update — recalculates all qualities and emits changes
|
|
420
|
+
* Periodic update — recalculates all qualities and emits changes.
|
|
421
|
+
* Stops the timer automatically when no peers are connected.
|
|
392
422
|
* @private
|
|
393
423
|
*/
|
|
394
424
|
_update() {
|
|
425
|
+
if (this._peers.size === 0) {
|
|
426
|
+
this.stop();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
395
429
|
for (const tracker of this._peers.values()) {
|
|
396
430
|
const quality = tracker.calculate();
|
|
397
431
|
if (quality.changed) {
|
|
@@ -12,7 +12,7 @@ const EventEmitter = require('../../utils/EventEmitter');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Health status levels
|
|
15
|
-
* @constant {
|
|
15
|
+
* @constant {any}
|
|
16
16
|
*/
|
|
17
17
|
const HEALTH_STATUS = Object.freeze({
|
|
18
18
|
GOOD: 'good',
|
|
@@ -23,7 +23,7 @@ const HEALTH_STATUS = Object.freeze({
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Default monitoring configuration
|
|
26
|
-
* @constant {
|
|
26
|
+
* @constant {any}
|
|
27
27
|
*/
|
|
28
28
|
const DEFAULT_CONFIG = Object.freeze({
|
|
29
29
|
/** Sample window size for latency calculations */
|
|
@@ -90,21 +90,21 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
90
90
|
class NetworkMonitor extends EventEmitter {
|
|
91
91
|
/**
|
|
92
92
|
* Creates a new NetworkMonitor instance.
|
|
93
|
-
* @param {
|
|
93
|
+
* @param {any} [options={}] - Configuration options
|
|
94
94
|
*/
|
|
95
95
|
constructor(options = {}) {
|
|
96
96
|
super();
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Configuration
|
|
100
|
-
* @type {
|
|
100
|
+
* @type {any}
|
|
101
101
|
* @private
|
|
102
102
|
*/
|
|
103
103
|
this._config = { ...DEFAULT_CONFIG, ...options };
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Node health tracking
|
|
107
|
-
* @type {Map<string,
|
|
107
|
+
* @type {Map<string, any>}
|
|
108
108
|
* @private
|
|
109
109
|
*/
|
|
110
110
|
this._nodes = new Map();
|
|
@@ -117,15 +117,36 @@ class NetworkMonitor extends EventEmitter {
|
|
|
117
117
|
this._pendingMessages = new Map();
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Latency samples
|
|
121
|
-
* @type {
|
|
120
|
+
* Latency samples (circular buffer)
|
|
121
|
+
* @type {Float64Array}
|
|
122
122
|
* @private
|
|
123
123
|
*/
|
|
124
|
-
this._latencies =
|
|
124
|
+
this._latencies = new Float64Array(this._config.latencySampleSize);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Circular buffer write index
|
|
128
|
+
* @type {number}
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
this._latencyIndex = 0;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Number of latency samples stored
|
|
135
|
+
* @type {number}
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
this._latencyCount = 0;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Running sum of latency samples for O(1) average
|
|
142
|
+
* @type {number}
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
this._latencySum = 0;
|
|
125
146
|
|
|
126
147
|
/**
|
|
127
148
|
* Global statistics
|
|
128
|
-
* @type {
|
|
149
|
+
* @type {any}
|
|
129
150
|
* @private
|
|
130
151
|
*/
|
|
131
152
|
this._stats = {
|
|
@@ -137,7 +158,7 @@ class NetworkMonitor extends EventEmitter {
|
|
|
137
158
|
|
|
138
159
|
/**
|
|
139
160
|
* Health check timer
|
|
140
|
-
* @type {
|
|
161
|
+
* @type {any}
|
|
141
162
|
* @private
|
|
142
163
|
*/
|
|
143
164
|
this._healthCheckTimer = null;
|
|
@@ -149,6 +170,13 @@ class NetworkMonitor extends EventEmitter {
|
|
|
149
170
|
*/
|
|
150
171
|
this._lastHealthReport = null;
|
|
151
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Previous health status for change detection
|
|
175
|
+
* @type {string|null}
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
this._previousHealth = null;
|
|
179
|
+
|
|
152
180
|
// Start health check timer
|
|
153
181
|
this._startHealthCheck();
|
|
154
182
|
}
|
|
@@ -330,7 +358,7 @@ class NetworkMonitor extends EventEmitter {
|
|
|
330
358
|
/**
|
|
331
359
|
* Gets detailed health for a specific node.
|
|
332
360
|
* @param {string} peerId - Peer ID
|
|
333
|
-
* @returns {
|
|
361
|
+
* @returns {any} Node health or null
|
|
334
362
|
*/
|
|
335
363
|
getNodeHealth(peerId) {
|
|
336
364
|
const node = this._nodes.get(peerId);
|
|
@@ -347,13 +375,13 @@ class NetworkMonitor extends EventEmitter {
|
|
|
347
375
|
|
|
348
376
|
/**
|
|
349
377
|
* Gets health information for all nodes.
|
|
350
|
-
* @returns {
|
|
378
|
+
* @returns {any[]} Array of node health
|
|
351
379
|
*/
|
|
352
380
|
getAllNodeHealth() {
|
|
353
381
|
const now = Date.now();
|
|
354
382
|
const timeout = this._config.nodeTimeoutMs;
|
|
355
383
|
|
|
356
|
-
return Array.from(this._nodes.values()).map(node => ({
|
|
384
|
+
return Array.from(this._nodes.values()).map((/** @type {any} */ node) => ({
|
|
357
385
|
...node,
|
|
358
386
|
isActive: (now - node.lastSeen) < timeout
|
|
359
387
|
}));
|
|
@@ -369,7 +397,7 @@ class NetworkMonitor extends EventEmitter {
|
|
|
369
397
|
|
|
370
398
|
/**
|
|
371
399
|
* Gets monitoring statistics.
|
|
372
|
-
* @returns {
|
|
400
|
+
* @returns {any} Statistics
|
|
373
401
|
*/
|
|
374
402
|
getStats() {
|
|
375
403
|
return {
|
|
@@ -386,7 +414,10 @@ class NetworkMonitor extends EventEmitter {
|
|
|
386
414
|
reset() {
|
|
387
415
|
this._nodes.clear();
|
|
388
416
|
this._pendingMessages.clear();
|
|
389
|
-
this._latencies =
|
|
417
|
+
this._latencies = new Float64Array(this._config.latencySampleSize);
|
|
418
|
+
this._latencyIndex = 0;
|
|
419
|
+
this._latencyCount = 0;
|
|
420
|
+
this._latencySum = 0;
|
|
390
421
|
this._stats = {
|
|
391
422
|
totalMessagesSent: 0,
|
|
392
423
|
totalMessagesDelivered: 0,
|
|
@@ -409,7 +440,7 @@ class NetworkMonitor extends EventEmitter {
|
|
|
409
440
|
/**
|
|
410
441
|
* Gets or creates a node entry.
|
|
411
442
|
* @param {string} peerId - Peer ID
|
|
412
|
-
* @returns {
|
|
443
|
+
* @returns {any} Node entry
|
|
413
444
|
* @private
|
|
414
445
|
*/
|
|
415
446
|
_getOrCreateNode(peerId) {
|
|
@@ -430,20 +461,27 @@ class NetworkMonitor extends EventEmitter {
|
|
|
430
461
|
}
|
|
431
462
|
|
|
432
463
|
/**
|
|
433
|
-
* Adds a latency sample.
|
|
464
|
+
* Adds a latency sample using circular buffer.
|
|
434
465
|
* @param {number} latency - Latency in ms
|
|
435
466
|
* @private
|
|
436
467
|
*/
|
|
437
468
|
_addLatencySample(latency) {
|
|
438
|
-
this._latencies.
|
|
439
|
-
if
|
|
440
|
-
|
|
469
|
+
const capacity = this._latencies.length;
|
|
470
|
+
// Subtract the old value being overwritten if buffer is full
|
|
471
|
+
if (this._latencyCount >= capacity) {
|
|
472
|
+
this._latencySum -= this._latencies[this._latencyIndex];
|
|
473
|
+
}
|
|
474
|
+
this._latencies[this._latencyIndex] = latency;
|
|
475
|
+
this._latencySum += latency;
|
|
476
|
+
this._latencyIndex = (this._latencyIndex + 1) % capacity;
|
|
477
|
+
if (this._latencyCount < capacity) {
|
|
478
|
+
this._latencyCount++;
|
|
441
479
|
}
|
|
442
480
|
}
|
|
443
481
|
|
|
444
482
|
/**
|
|
445
483
|
* Updates node latency with exponential moving average.
|
|
446
|
-
* @param {
|
|
484
|
+
* @param {any} node - Node to update
|
|
447
485
|
* @param {number} latency - New latency sample
|
|
448
486
|
* @private
|
|
449
487
|
*/
|
|
@@ -458,7 +496,7 @@ class NetworkMonitor extends EventEmitter {
|
|
|
458
496
|
|
|
459
497
|
/**
|
|
460
498
|
* Updates node packet loss rate.
|
|
461
|
-
* @param {
|
|
499
|
+
* @param {any} node - Node to update
|
|
462
500
|
* @private
|
|
463
501
|
*/
|
|
464
502
|
_updateNodePacketLoss(node) {
|
|
@@ -469,17 +507,30 @@ class NetworkMonitor extends EventEmitter {
|
|
|
469
507
|
}
|
|
470
508
|
|
|
471
509
|
/**
|
|
472
|
-
* Calculates average latency from samples.
|
|
510
|
+
* Calculates average latency from samples (O(1) using running sum).
|
|
473
511
|
* @returns {number} Average latency
|
|
474
512
|
* @private
|
|
475
513
|
*/
|
|
476
514
|
_calculateAverageLatency() {
|
|
477
|
-
if (this.
|
|
515
|
+
if (this._latencyCount === 0) {
|
|
478
516
|
return 0;
|
|
479
517
|
}
|
|
480
|
-
return Math.round(
|
|
481
|
-
|
|
482
|
-
|
|
518
|
+
return Math.round(this._latencySum / this._latencyCount);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Cleans up stale pending messages older than nodeTimeoutMs.
|
|
523
|
+
* @private
|
|
524
|
+
*/
|
|
525
|
+
_cleanupPendingMessages() {
|
|
526
|
+
const now = Date.now();
|
|
527
|
+
const timeout = this._config.nodeTimeoutMs;
|
|
528
|
+
for (const [messageId, pending] of this._pendingMessages) {
|
|
529
|
+
if (now - pending.timestamp > timeout) {
|
|
530
|
+
this._pendingMessages.delete(messageId);
|
|
531
|
+
this._stats.totalMessagesFailed++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
483
534
|
}
|
|
484
535
|
|
|
485
536
|
/**
|
|
@@ -520,9 +571,10 @@ class NetworkMonitor extends EventEmitter {
|
|
|
520
571
|
this._healthCheckTimer = setInterval(
|
|
521
572
|
() => {
|
|
522
573
|
try {
|
|
574
|
+
this._cleanupPendingMessages();
|
|
523
575
|
const report = this.generateHealthReport();
|
|
524
576
|
this.emit('health-report', report);
|
|
525
|
-
} catch (
|
|
577
|
+
} catch (_error) {
|
|
526
578
|
// Don't let health check errors crash the monitor
|
|
527
579
|
}
|
|
528
580
|
},
|
package/src/mesh/peer/Peer.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
const { CONNECTION_STATE } = require('../../constants');
|
|
9
9
|
const { ValidationError } = require('../../errors');
|
|
10
10
|
|
|
11
|
+
/** Pre-computed Set of valid connection states for O(1) lookup */
|
|
12
|
+
const CONNECTION_STATE_SET = new Set(Object.values(CONNECTION_STATE));
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* Represents a peer in the mesh network
|
|
13
16
|
* @class Peer
|
|
@@ -15,12 +18,7 @@ const { ValidationError } = require('../../errors');
|
|
|
15
18
|
class Peer {
|
|
16
19
|
/**
|
|
17
20
|
* Creates a new Peer
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {string} options.id - Unique peer identifier
|
|
20
|
-
* @param {Uint8Array} [options.publicKey] - Peer's public key
|
|
21
|
-
* @param {string} [options.name] - Display name
|
|
22
|
-
* @param {number} [options.rssi] - Signal strength
|
|
23
|
-
* @param {number} [options.hopCount] - Distance in hops
|
|
21
|
+
* @param {any} options - Peer options *
|
|
24
22
|
*/
|
|
25
23
|
constructor(options) {
|
|
26
24
|
if (!options || typeof options.id !== 'string' || options.id.length === 0) {
|
|
@@ -85,7 +83,7 @@ class Peer {
|
|
|
85
83
|
|
|
86
84
|
/**
|
|
87
85
|
* Additional metadata
|
|
88
|
-
* @type {
|
|
86
|
+
* @type {any}
|
|
89
87
|
*/
|
|
90
88
|
this.metadata = options.metadata || {};
|
|
91
89
|
}
|
|
@@ -130,9 +128,9 @@ class Peer {
|
|
|
130
128
|
* @param {string} state - New connection state
|
|
131
129
|
*/
|
|
132
130
|
setConnectionState(state) {
|
|
133
|
-
if (!
|
|
131
|
+
if (!CONNECTION_STATE_SET.has(state)) {
|
|
134
132
|
throw ValidationError.invalidArgument('state', state, {
|
|
135
|
-
validValues:
|
|
133
|
+
validValues: Array.from(CONNECTION_STATE_SET)
|
|
136
134
|
});
|
|
137
135
|
}
|
|
138
136
|
this.connectionState = state;
|
|
@@ -198,7 +196,7 @@ class Peer {
|
|
|
198
196
|
|
|
199
197
|
/**
|
|
200
198
|
* Converts peer to a JSON-serializable object
|
|
201
|
-
* @returns {
|
|
199
|
+
* @returns {any} JSON representation
|
|
202
200
|
*/
|
|
203
201
|
toJSON() {
|
|
204
202
|
return {
|
|
@@ -219,7 +217,7 @@ class Peer {
|
|
|
219
217
|
|
|
220
218
|
/**
|
|
221
219
|
* Creates a Peer from JSON data
|
|
222
|
-
* @param {
|
|
220
|
+
* @param {any} data - JSON data
|
|
223
221
|
* @returns {Peer} New Peer instance
|
|
224
222
|
*/
|
|
225
223
|
static fromJSON(data) {
|
|
@@ -16,7 +16,7 @@ const { ValidationError } = require('../../errors');
|
|
|
16
16
|
* @property {string} [name] - Display name
|
|
17
17
|
* @property {number[]} [publicKey] - Public key bytes
|
|
18
18
|
* @property {number} [hopCount] - Hop count
|
|
19
|
-
* @property {
|
|
19
|
+
* @property {any} [capabilities] - Peer capabilities
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -27,10 +27,7 @@ const { ValidationError } = require('../../errors');
|
|
|
27
27
|
class PeerDiscovery extends EventEmitter {
|
|
28
28
|
/**
|
|
29
29
|
* Creates a new PeerDiscovery handler
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {string} options.localPeerId - Local peer's ID
|
|
32
|
-
* @param {Uint8Array} [options.publicKey] - Local peer's public key
|
|
33
|
-
* @param {string} [options.displayName] - Local display name
|
|
30
|
+
* @param {any} options - Configuration options *
|
|
34
31
|
*/
|
|
35
32
|
constructor(options = {}) {
|
|
36
33
|
super();
|
|
@@ -59,13 +56,13 @@ class PeerDiscovery extends EventEmitter {
|
|
|
59
56
|
|
|
60
57
|
/**
|
|
61
58
|
* Capabilities advertised to peers
|
|
62
|
-
* @type {
|
|
59
|
+
* @type {any}
|
|
63
60
|
*/
|
|
64
61
|
this.capabilities = options.capabilities || {};
|
|
65
62
|
|
|
66
63
|
/**
|
|
67
64
|
* Discovery statistics
|
|
68
|
-
* @type {
|
|
65
|
+
* @type {any}
|
|
69
66
|
* @private
|
|
70
67
|
*/
|
|
71
68
|
this._stats = {
|
|
@@ -80,9 +77,10 @@ class PeerDiscovery extends EventEmitter {
|
|
|
80
77
|
|
|
81
78
|
/**
|
|
82
79
|
* Creates an announce message payload
|
|
83
|
-
* @returns {
|
|
80
|
+
* @returns {any} Announce payload
|
|
84
81
|
*/
|
|
85
82
|
createAnnouncePayload() {
|
|
83
|
+
/** @type {any} */
|
|
86
84
|
const payload = {
|
|
87
85
|
peerId: this.localPeerId,
|
|
88
86
|
name: this.displayName,
|
|
@@ -100,8 +98,9 @@ class PeerDiscovery extends EventEmitter {
|
|
|
100
98
|
/**
|
|
101
99
|
* Creates a peer request payload
|
|
102
100
|
* @param {string} [targetPeerId] - Specific peer to request (null for broadcast)
|
|
103
|
-
* @returns {
|
|
101
|
+
* @returns {any} Request payload
|
|
104
102
|
*/
|
|
103
|
+
// @ts-ignore
|
|
105
104
|
createRequestPayload(targetPeerId = null) {
|
|
106
105
|
return {
|
|
107
106
|
requesterId: this.localPeerId,
|
|
@@ -113,7 +112,7 @@ class PeerDiscovery extends EventEmitter {
|
|
|
113
112
|
/**
|
|
114
113
|
* Creates a peer response payload
|
|
115
114
|
* @param {string} requesterId - ID of requesting peer
|
|
116
|
-
* @returns {
|
|
115
|
+
* @returns {any} Response payload
|
|
117
116
|
*/
|
|
118
117
|
createResponsePayload(requesterId) {
|
|
119
118
|
return {
|
|
@@ -129,9 +128,9 @@ class PeerDiscovery extends EventEmitter {
|
|
|
129
128
|
/**
|
|
130
129
|
* Processes an incoming discovery message
|
|
131
130
|
* @param {number} type - Message type
|
|
132
|
-
* @param {
|
|
131
|
+
* @param {any} payload - Message payload
|
|
133
132
|
* @param {string} sourcePeerId - Source peer ID
|
|
134
|
-
* @returns {
|
|
133
|
+
* @returns {any} Response to send, or null
|
|
135
134
|
*/
|
|
136
135
|
processMessage(type, payload, sourcePeerId) {
|
|
137
136
|
// Ignore our own messages
|
|
@@ -158,7 +157,7 @@ class PeerDiscovery extends EventEmitter {
|
|
|
158
157
|
|
|
159
158
|
/**
|
|
160
159
|
* Handles a peer announce message
|
|
161
|
-
* @param {
|
|
160
|
+
* @param {any} payload - Announce payload
|
|
162
161
|
* @param {string} sourcePeerId - Source peer ID
|
|
163
162
|
* @returns {null} No response needed
|
|
164
163
|
* @private
|
|
@@ -181,9 +180,9 @@ class PeerDiscovery extends EventEmitter {
|
|
|
181
180
|
|
|
182
181
|
/**
|
|
183
182
|
* Handles a peer request message
|
|
184
|
-
* @param {
|
|
185
|
-
* @param {string}
|
|
186
|
-
* @returns {
|
|
183
|
+
* @param {any} payload - Request payload
|
|
184
|
+
* @param {string} _sourcePeerId - Source peer ID
|
|
185
|
+
* @returns {any} Response payload or null
|
|
187
186
|
* @private
|
|
188
187
|
*/
|
|
189
188
|
_handleRequest(payload, _sourcePeerId) {
|
|
@@ -203,7 +202,7 @@ class PeerDiscovery extends EventEmitter {
|
|
|
203
202
|
|
|
204
203
|
/**
|
|
205
204
|
* Handles a peer response message
|
|
206
|
-
* @param {
|
|
205
|
+
* @param {any} payload - Response payload
|
|
207
206
|
* @param {string} sourcePeerId - Source peer ID
|
|
208
207
|
* @returns {null} No response needed
|
|
209
208
|
* @private
|
|
@@ -230,7 +229,7 @@ class PeerDiscovery extends EventEmitter {
|
|
|
230
229
|
|
|
231
230
|
/**
|
|
232
231
|
* Updates local peer information
|
|
233
|
-
* @param {
|
|
232
|
+
* @param {any} info - New info
|
|
234
233
|
*/
|
|
235
234
|
updateLocalInfo(info) {
|
|
236
235
|
if (info.displayName !== undefined) { this.displayName = info.displayName; }
|
|
@@ -242,7 +241,7 @@ class PeerDiscovery extends EventEmitter {
|
|
|
242
241
|
|
|
243
242
|
/**
|
|
244
243
|
* Gets discovery statistics
|
|
245
|
-
* @returns {
|
|
244
|
+
* @returns {any} Statistics
|
|
246
245
|
*/
|
|
247
246
|
getStats() {
|
|
248
247
|
return { ...this._stats };
|