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
@@ -0,0 +1,408 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Connection Quality Calculator
5
+ * @module mesh/monitor/ConnectionQuality
6
+ *
7
+ * Calculates real-time connection quality per peer based on
8
+ * RSSI, latency, packet loss, and throughput metrics.
9
+ */
10
+
11
+ const EventEmitter = require('../../utils/EventEmitter');
12
+
13
+ /**
14
+ * Connection quality levels
15
+ * @constant {Object}
16
+ */
17
+ const QUALITY_LEVEL = Object.freeze({
18
+ EXCELLENT: 'excellent',
19
+ GOOD: 'good',
20
+ FAIR: 'fair',
21
+ POOR: 'poor',
22
+ DISCONNECTED: 'disconnected'
23
+ });
24
+
25
+ /**
26
+ * Default quality configuration
27
+ * @constant {Object}
28
+ */
29
+ const DEFAULT_CONFIG = Object.freeze({
30
+ /** How often to recalculate quality (ms) */
31
+ updateIntervalMs: 5000,
32
+ /** RSSI thresholds (dBm) */
33
+ rssiThresholds: { excellent: -50, good: -70, fair: -85 },
34
+ /** Latency thresholds (ms) */
35
+ latencyThresholds: { excellent: 100, good: 300, fair: 1000 },
36
+ /** Packet loss thresholds (ratio 0-1) */
37
+ packetLossThresholds: { excellent: 0.01, good: 0.05, fair: 0.15 },
38
+ /** Throughput thresholds (kbps) */
39
+ throughputThresholds: { excellent: 200, good: 100, fair: 50 },
40
+ /** Scoring weights */
41
+ weights: { rssi: 0.3, latency: 0.3, packetLoss: 0.25, throughput: 0.15 },
42
+ /** Number of samples to keep per metric */
43
+ sampleSize: 20,
44
+ /** Peer timeout — mark disconnected after this (ms) */
45
+ peerTimeoutMs: 30000
46
+ });
47
+
48
+ /**
49
+ * Per-peer quality metrics tracker
50
+ * @class PeerQualityTracker
51
+ */
52
+ class PeerQualityTracker {
53
+ constructor(peerId, config) {
54
+ this.peerId = peerId;
55
+ this._config = config;
56
+ this._rssiSamples = [];
57
+ this._latencySamples = [];
58
+ this._packetsSent = 0;
59
+ this._packetsAcked = 0;
60
+ this._bytesTransferred = 0;
61
+ this._transferStartTime = null;
62
+ this._lastActivity = Date.now();
63
+ this._transport = 'ble';
64
+ this._currentLevel = QUALITY_LEVEL.GOOD;
65
+ }
66
+
67
+ /**
68
+ * Records an RSSI sample
69
+ * @param {number} rssi - Signal strength in dBm
70
+ */
71
+ recordRssi(rssi) {
72
+ this._rssiSamples.push(rssi);
73
+ if (this._rssiSamples.length > this._config.sampleSize) {
74
+ this._rssiSamples.shift();
75
+ }
76
+ this._lastActivity = Date.now();
77
+ }
78
+
79
+ /**
80
+ * Records a latency measurement
81
+ * @param {number} latencyMs - Round-trip latency in ms
82
+ */
83
+ recordLatency(latencyMs) {
84
+ this._latencySamples.push(latencyMs);
85
+ if (this._latencySamples.length > this._config.sampleSize) {
86
+ this._latencySamples.shift();
87
+ }
88
+ this._lastActivity = Date.now();
89
+ }
90
+
91
+ /**
92
+ * Records a sent packet (for packet loss calculation)
93
+ */
94
+ recordPacketSent() {
95
+ this._packetsSent++;
96
+ this._lastActivity = Date.now();
97
+ }
98
+
99
+ /**
100
+ * Records a received acknowledgment
101
+ */
102
+ recordPacketAcked() {
103
+ this._packetsAcked++;
104
+ this._lastActivity = Date.now();
105
+ }
106
+
107
+ /**
108
+ * Records bytes transferred (for throughput calculation)
109
+ * @param {number} bytes - Number of bytes
110
+ */
111
+ recordBytesTransferred(bytes) {
112
+ if (!this._transferStartTime) {
113
+ this._transferStartTime = Date.now();
114
+ }
115
+ this._bytesTransferred += bytes;
116
+ this._lastActivity = Date.now();
117
+ }
118
+
119
+ /**
120
+ * Sets the active transport type
121
+ * @param {string} transport - Transport name ('ble', 'wifi-direct', etc.)
122
+ */
123
+ setTransport(transport) {
124
+ this._transport = transport;
125
+ }
126
+
127
+ /**
128
+ * Gets the average RSSI
129
+ * @returns {number|null}
130
+ */
131
+ getAvgRssi() {
132
+ if (this._rssiSamples.length === 0) { return null; }
133
+ return this._rssiSamples.reduce((a, b) => a + b, 0) / this._rssiSamples.length;
134
+ }
135
+
136
+ /**
137
+ * Gets the average latency
138
+ * @returns {number|null}
139
+ */
140
+ getAvgLatency() {
141
+ if (this._latencySamples.length === 0) { return null; }
142
+ return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;
143
+ }
144
+
145
+ /**
146
+ * Gets the packet loss ratio
147
+ * @returns {number}
148
+ */
149
+ getPacketLoss() {
150
+ if (this._packetsSent === 0) { return 0; }
151
+ return 1 - (this._packetsAcked / this._packetsSent);
152
+ }
153
+
154
+ /**
155
+ * Gets estimated throughput in kbps
156
+ * @returns {number}
157
+ */
158
+ getThroughputKbps() {
159
+ if (!this._transferStartTime || this._bytesTransferred === 0) { return 0; }
160
+ const elapsedMs = Date.now() - this._transferStartTime;
161
+ if (elapsedMs === 0) { return 0; }
162
+ return (this._bytesTransferred * 8) / elapsedMs; // kbps
163
+ }
164
+
165
+ /**
166
+ * Checks if peer has timed out
167
+ * @returns {boolean}
168
+ */
169
+ isTimedOut() {
170
+ return (Date.now() - this._lastActivity) > this._config.peerTimeoutMs;
171
+ }
172
+
173
+ /**
174
+ * Calculates quality score (0-1) for a metric
175
+ * @param {number|null} value - Metric value
176
+ * @param {Object} thresholds - {excellent, good, fair}
177
+ * @param {boolean} [higherIsBetter=false]
178
+ * @returns {number}
179
+ */
180
+ _scoreMetric(value, thresholds, higherIsBetter = false) {
181
+ if (value === null || value === undefined) { return 0.5; } // neutral
182
+
183
+ if (higherIsBetter) {
184
+ if (value >= thresholds.excellent) { return 1.0; }
185
+ if (value >= thresholds.good) { return 0.75; }
186
+ if (value >= thresholds.fair) { return 0.5; }
187
+ return 0.25;
188
+ } else {
189
+ // Lower is better (latency, packet loss)
190
+ if (value <= thresholds.excellent) { return 1.0; }
191
+ if (value <= thresholds.good) { return 0.75; }
192
+ if (value <= thresholds.fair) { return 0.5; }
193
+ return 0.25;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Calculates overall quality
199
+ * @returns {Object} Quality report
200
+ */
201
+ calculate() {
202
+ if (this.isTimedOut()) {
203
+ return {
204
+ peerId: this.peerId,
205
+ level: QUALITY_LEVEL.DISCONNECTED,
206
+ score: 0,
207
+ rssi: null,
208
+ latencyMs: null,
209
+ packetLoss: 0,
210
+ throughputKbps: 0,
211
+ transport: this._transport,
212
+ lastUpdated: this._lastActivity
213
+ };
214
+ }
215
+
216
+ const rssi = this.getAvgRssi();
217
+ const latency = this.getAvgLatency();
218
+ const packetLoss = this.getPacketLoss();
219
+ const throughput = this.getThroughputKbps();
220
+
221
+ const w = this._config.weights;
222
+ const rssiScore = this._scoreMetric(rssi, this._config.rssiThresholds, true);
223
+ const latencyScore = this._scoreMetric(latency, this._config.latencyThresholds, false);
224
+ const plScore = this._scoreMetric(packetLoss, this._config.packetLossThresholds, false);
225
+ const tpScore = this._scoreMetric(throughput, this._config.throughputThresholds, true);
226
+
227
+ const score = (rssiScore * w.rssi) + (latencyScore * w.latency) +
228
+ (plScore * w.packetLoss) + (tpScore * w.throughput);
229
+
230
+ let level;
231
+ 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
+
233
+ const previousLevel = this._currentLevel;
234
+ this._currentLevel = level;
235
+
236
+ return {
237
+ peerId: this.peerId,
238
+ level,
239
+ score: Math.round(score * 100) / 100,
240
+ rssi: rssi !== null ? Math.round(rssi) : null,
241
+ latencyMs: latency !== null ? Math.round(latency) : null,
242
+ packetLoss: Math.round(packetLoss * 1000) / 1000,
243
+ throughputKbps: Math.round(throughput),
244
+ transport: this._transport,
245
+ lastUpdated: Date.now(),
246
+ changed: previousLevel !== level
247
+ };
248
+ }
249
+ }
250
+
251
+ /**
252
+ * ConnectionQuality — manages quality tracking for all peers
253
+ * @class ConnectionQuality
254
+ * @extends EventEmitter
255
+ */
256
+ class ConnectionQuality extends EventEmitter {
257
+ /**
258
+ * @param {Object} [config={}]
259
+ */
260
+ constructor(config = {}) {
261
+ super();
262
+ this._config = { ...DEFAULT_CONFIG, ...config };
263
+ /** @type {Map<string, PeerQualityTracker>} */
264
+ this._peers = new Map();
265
+ this._updateTimer = null;
266
+ }
267
+
268
+ /**
269
+ * Starts periodic quality updates
270
+ */
271
+ start() {
272
+ if (this._updateTimer) { return; }
273
+ this._updateTimer = setInterval(() => this._update(), this._config.updateIntervalMs);
274
+ }
275
+
276
+ /**
277
+ * Stops periodic updates
278
+ */
279
+ stop() {
280
+ if (this._updateTimer) {
281
+ clearInterval(this._updateTimer);
282
+ this._updateTimer = null;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Destroys and cleans up
288
+ */
289
+ destroy() {
290
+ this.stop();
291
+ this._peers.clear();
292
+ this.removeAllListeners();
293
+ }
294
+
295
+ /**
296
+ * Gets or creates a tracker for a peer
297
+ * @param {string} peerId
298
+ * @returns {PeerQualityTracker}
299
+ */
300
+ _getTracker(peerId) {
301
+ if (!this._peers.has(peerId)) {
302
+ this._peers.set(peerId, new PeerQualityTracker(peerId, this._config));
303
+ }
304
+ return this._peers.get(peerId);
305
+ }
306
+
307
+ /**
308
+ * Records RSSI for a peer
309
+ * @param {string} peerId
310
+ * @param {number} rssi
311
+ */
312
+ recordRssi(peerId, rssi) {
313
+ this._getTracker(peerId).recordRssi(rssi);
314
+ }
315
+
316
+ /**
317
+ * Records latency for a peer
318
+ * @param {string} peerId
319
+ * @param {number} latencyMs
320
+ */
321
+ recordLatency(peerId, latencyMs) {
322
+ this._getTracker(peerId).recordLatency(latencyMs);
323
+ }
324
+
325
+ /**
326
+ * Records a sent packet
327
+ * @param {string} peerId
328
+ */
329
+ recordPacketSent(peerId) {
330
+ this._getTracker(peerId).recordPacketSent();
331
+ }
332
+
333
+ /**
334
+ * Records a received ack
335
+ * @param {string} peerId
336
+ */
337
+ recordPacketAcked(peerId) {
338
+ this._getTracker(peerId).recordPacketAcked();
339
+ }
340
+
341
+ /**
342
+ * Records bytes transferred
343
+ * @param {string} peerId
344
+ * @param {number} bytes
345
+ */
346
+ recordBytesTransferred(peerId, bytes) {
347
+ this._getTracker(peerId).recordBytesTransferred(bytes);
348
+ }
349
+
350
+ /**
351
+ * Sets transport type for a peer
352
+ * @param {string} peerId
353
+ * @param {string} transport
354
+ */
355
+ setTransport(peerId, transport) {
356
+ this._getTracker(peerId).setTransport(transport);
357
+ }
358
+
359
+ /**
360
+ * Removes a peer tracker
361
+ * @param {string} peerId
362
+ */
363
+ removePeer(peerId) {
364
+ this._peers.delete(peerId);
365
+ }
366
+
367
+ /**
368
+ * Gets quality for a specific peer
369
+ * @param {string} peerId
370
+ * @returns {Object|null}
371
+ */
372
+ getQuality(peerId) {
373
+ const tracker = this._peers.get(peerId);
374
+ if (!tracker) { return null; }
375
+ return tracker.calculate();
376
+ }
377
+
378
+ /**
379
+ * Gets quality for all peers
380
+ * @returns {Object[]}
381
+ */
382
+ getAllQuality() {
383
+ const results = [];
384
+ for (const tracker of this._peers.values()) {
385
+ results.push(tracker.calculate());
386
+ }
387
+ return results;
388
+ }
389
+
390
+ /**
391
+ * Periodic update — recalculates all qualities and emits changes
392
+ * @private
393
+ */
394
+ _update() {
395
+ for (const tracker of this._peers.values()) {
396
+ const quality = tracker.calculate();
397
+ if (quality.changed) {
398
+ this.emit('qualityChanged', quality);
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ module.exports = {
405
+ ConnectionQuality,
406
+ PeerQualityTracker,
407
+ QUALITY_LEVEL
408
+ };