react-native-ble-mesh 1.1.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +183 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +667 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +90 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +126 -0
- package/src/crypto/providers/QuickCryptoProvider.js +158 -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/AppStateManager.js +9 -1
- package/src/hooks/useMesh.js +47 -13
- package/src/hooks/useMessages.js +6 -4
- package/src/hooks/usePeers.js +13 -9
- package/src/index.js +23 -8
- package/src/mesh/dedup/BloomFilter.js +44 -57
- package/src/mesh/dedup/DedupManager.js +67 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/fragment/Fragmenter.js +1 -1
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +433 -0
- package/src/mesh/monitor/NetworkMonitor.js +376 -320
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/Peer.js +5 -2
- package/src/mesh/peer/PeerManager.js +21 -4
- package/src/mesh/router/MessageRouter.js +38 -19
- package/src/mesh/router/RouteTable.js +24 -8
- package/src/mesh/store/StoreAndForwardManager.js +305 -296
- package/src/mesh/store/index.js +1 -1
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +15 -3
- package/src/protocol/serializer.js +7 -10
- package/src/protocol/validator.js +23 -5
- package/src/service/BatteryOptimizer.js +285 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +163 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +79 -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/TextManager.js +21 -15
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/storage/MessageStore.js +55 -2
- package/src/transport/BLETransport.js +141 -10
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +330 -0
- package/src/transport/WiFiDirectTransport.js +296 -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/EventEmitter.js +6 -9
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +293 -291
- package/src/utils/encoding.js +33 -8
- 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
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { NetworkMonitor, HEALTH_STATUS, DEFAULT_CONFIG } = require('./NetworkMonitor');
|
|
9
|
+
const { ConnectionQuality, PeerQualityTracker, QUALITY_LEVEL } = require('./ConnectionQuality');
|
|
9
10
|
|
|
10
11
|
module.exports = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
NetworkMonitor,
|
|
13
|
+
HEALTH_STATUS,
|
|
14
|
+
DEFAULT_CONFIG,
|
|
15
|
+
ConnectionQuality,
|
|
16
|
+
PeerQualityTracker,
|
|
17
|
+
QUALITY_LEVEL
|
|
14
18
|
};
|
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
|
|
@@ -130,9 +133,9 @@ class Peer {
|
|
|
130
133
|
* @param {string} state - New connection state
|
|
131
134
|
*/
|
|
132
135
|
setConnectionState(state) {
|
|
133
|
-
if (!
|
|
136
|
+
if (!CONNECTION_STATE_SET.has(state)) {
|
|
134
137
|
throw ValidationError.invalidArgument('state', state, {
|
|
135
|
-
validValues:
|
|
138
|
+
validValues: Array.from(CONNECTION_STATE_SET)
|
|
136
139
|
});
|
|
137
140
|
}
|
|
138
141
|
this.connectionState = state;
|
|
@@ -109,7 +109,11 @@ class PeerManager extends EventEmitter {
|
|
|
109
109
|
* @returns {Peer[]} Array of connected peers
|
|
110
110
|
*/
|
|
111
111
|
getConnectedPeers() {
|
|
112
|
-
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const peer of this._peers.values()) {
|
|
114
|
+
if (peer.isConnected()) { result.push(peer); }
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
/**
|
|
@@ -117,7 +121,11 @@ class PeerManager extends EventEmitter {
|
|
|
117
121
|
* @returns {Peer[]} Array of secured peers
|
|
118
122
|
*/
|
|
119
123
|
getSecuredPeers() {
|
|
120
|
-
|
|
124
|
+
const result = [];
|
|
125
|
+
for (const peer of this._peers.values()) {
|
|
126
|
+
if (peer.isSecured()) { result.push(peer); }
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
/**
|
|
@@ -125,7 +133,11 @@ class PeerManager extends EventEmitter {
|
|
|
125
133
|
* @returns {Peer[]} Array of direct peers
|
|
126
134
|
*/
|
|
127
135
|
getDirectPeers() {
|
|
128
|
-
|
|
136
|
+
const result = [];
|
|
137
|
+
for (const peer of this._peers.values()) {
|
|
138
|
+
if (peer.isDirect()) { result.push(peer); }
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
/**
|
|
@@ -230,7 +242,12 @@ class PeerManager extends EventEmitter {
|
|
|
230
242
|
cleanup(maxAge = this.peerTimeout) {
|
|
231
243
|
const removed = [];
|
|
232
244
|
for (const [id, peer] of this._peers) {
|
|
233
|
-
|
|
245
|
+
// Don't remove peers that are actively connecting or connected
|
|
246
|
+
if (peer.isConnected() || peer.connectionState === CONNECTION_STATE.CONNECTING) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (peer.isStale(maxAge)) {
|
|
234
251
|
this._peers.delete(id);
|
|
235
252
|
removed.push(peer);
|
|
236
253
|
this.emit(EVENTS.PEER_LOST, peer);
|
|
@@ -12,17 +12,26 @@ const { DedupManager } = require('../dedup');
|
|
|
12
12
|
const RouteTable = require('./RouteTable');
|
|
13
13
|
const { randomBytes } = require('../../utils/bytes');
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Hex lookup table for fast byte-to-hex conversion
|
|
17
|
+
* @constant {string[]}
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
const HEX = Array.from({ length: 256 }, (_, i) => (i < 16 ? '0' : '') + i.toString(16));
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* Generates a UUID v4 string
|
|
17
24
|
* @returns {string} UUID string
|
|
18
25
|
* @private
|
|
19
26
|
*/
|
|
20
27
|
function generateUUID() {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const b = randomBytes(16);
|
|
29
|
+
b[6] = (b[6] & 0x0f) | 0x40;
|
|
30
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
31
|
+
return `${HEX[b[0]]}${HEX[b[1]]}${HEX[b[2]]}${HEX[b[3]]}-${
|
|
32
|
+
HEX[b[4]]}${HEX[b[5]]}-${HEX[b[6]]}${HEX[b[7]]}-${
|
|
33
|
+
HEX[b[8]]}${HEX[b[9]]}-${HEX[b[10]]}${HEX[b[11]]}${
|
|
34
|
+
HEX[b[12]]}${HEX[b[13]]}${HEX[b[14]]}${HEX[b[15]]}`;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
/**
|
|
@@ -148,8 +157,8 @@ class MessageRouter extends EventEmitter {
|
|
|
148
157
|
return null;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
// Check hop count
|
|
152
|
-
if (hopCount
|
|
160
|
+
// Check hop count (allow delivery at exactly maxHops, just don't relay further)
|
|
161
|
+
if (hopCount > (maxHops || MESH_CONFIG.MAX_HOPS)) {
|
|
153
162
|
this._stats.maxHopsDropped++;
|
|
154
163
|
this.emit(EVENTS.MESSAGE_DROPPED, { messageId, reason: 'max_hops' });
|
|
155
164
|
return null;
|
|
@@ -165,17 +174,18 @@ class MessageRouter extends EventEmitter {
|
|
|
165
174
|
this._routeTable.addRoute(sourcePeerId, sourcePeerId, 0);
|
|
166
175
|
|
|
167
176
|
// Check if message is for us
|
|
168
|
-
const isForUs = !message.recipientId ||
|
|
169
|
-
|
|
170
|
-
(message.flags & MESSAGE_FLAGS.IS_BROADCAST);
|
|
177
|
+
const isForUs = !message.recipientId || message.recipientId === this.localPeerId;
|
|
178
|
+
const isBroadcast = (message.flags & MESSAGE_FLAGS.IS_BROADCAST) !== 0;
|
|
171
179
|
|
|
172
|
-
if
|
|
180
|
+
// Deliver locally if for us or broadcast
|
|
181
|
+
if (isForUs || isBroadcast) {
|
|
173
182
|
this.emit(EVENTS.MESSAGE_RECEIVED, message);
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
// Relay if
|
|
177
|
-
const shouldRelay =
|
|
178
|
-
|
|
185
|
+
// Relay decision: only relay if TTL allows AND message isn't exclusively for us
|
|
186
|
+
const shouldRelay = isBroadcast
|
|
187
|
+
? (message.hopCount < (maxHops || MESH_CONFIG.MAX_HOPS)) // Broadcasts relay if hops remain
|
|
188
|
+
: (!isForUs); // Unicast only relays if not for us
|
|
179
189
|
|
|
180
190
|
if (shouldRelay) {
|
|
181
191
|
this._relayMessage(message, sourcePeerId);
|
|
@@ -237,8 +247,11 @@ class MessageRouter extends EventEmitter {
|
|
|
237
247
|
return [nextHop];
|
|
238
248
|
}
|
|
239
249
|
|
|
240
|
-
// No known route,
|
|
241
|
-
|
|
250
|
+
// No known route - use limited flooding (max 3 peers, prefer recently active)
|
|
251
|
+
const allPeers = Array.from(this._peers.keys()).filter(id => id !== excludePeerId);
|
|
252
|
+
// Limit flood scope to prevent network storms
|
|
253
|
+
const maxFloodPeers = Math.min(3, allPeers.length);
|
|
254
|
+
return allPeers.slice(0, maxFloodPeers);
|
|
242
255
|
}
|
|
243
256
|
|
|
244
257
|
/**
|
|
@@ -270,25 +283,31 @@ class MessageRouter extends EventEmitter {
|
|
|
270
283
|
expiresAt: now + MESH_CONFIG.MESSAGE_TTL_MS
|
|
271
284
|
};
|
|
272
285
|
|
|
273
|
-
this._dedupManager.markSeen(messageId);
|
|
274
|
-
this._stats.messagesSent++;
|
|
275
|
-
|
|
276
286
|
// Determine targets
|
|
277
287
|
const targets = recipientId
|
|
278
288
|
? [this._routeTable.getNextHop(recipientId) || recipientId]
|
|
279
289
|
: Array.from(this._peers.keys());
|
|
280
290
|
|
|
291
|
+
// Send to targets
|
|
292
|
+
let anySent = false;
|
|
281
293
|
for (const peerId of targets) {
|
|
282
294
|
const sendFn = this._peers.get(peerId);
|
|
283
295
|
if (sendFn) {
|
|
284
296
|
try {
|
|
285
297
|
sendFn(message);
|
|
298
|
+
anySent = true;
|
|
286
299
|
} catch (err) {
|
|
287
300
|
this.emit(EVENTS.ERROR, err);
|
|
288
301
|
}
|
|
289
302
|
}
|
|
290
303
|
}
|
|
291
304
|
|
|
305
|
+
// Only mark as seen AFTER successful send
|
|
306
|
+
if (anySent) {
|
|
307
|
+
this._dedupManager.markSeen(messageId);
|
|
308
|
+
this._stats.messagesSent++;
|
|
309
|
+
}
|
|
310
|
+
|
|
292
311
|
this.emit(EVENTS.MESSAGE_SENT, message);
|
|
293
312
|
return messageId;
|
|
294
313
|
}
|
|
@@ -99,7 +99,13 @@ class RouteTable {
|
|
|
99
99
|
|
|
100
100
|
// Enforce max routes limit
|
|
101
101
|
if (!existingRoute && this._routes.size >= this.maxRoutes) {
|
|
102
|
-
|
|
102
|
+
// First try to clean up expired routes before evicting valid ones
|
|
103
|
+
this.cleanup();
|
|
104
|
+
|
|
105
|
+
// If still at capacity after cleanup, evict oldest
|
|
106
|
+
if (this._routes.size >= this.maxRoutes) {
|
|
107
|
+
this._evictOldestRoute();
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
const route = {
|
|
@@ -275,16 +281,26 @@ class RouteTable {
|
|
|
275
281
|
* @returns {Object} Statistics
|
|
276
282
|
*/
|
|
277
283
|
getStats() {
|
|
278
|
-
const
|
|
279
|
-
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
let totalRoutes = 0;
|
|
286
|
+
let maxHops = 0;
|
|
287
|
+
let hopSum = 0;
|
|
288
|
+
|
|
289
|
+
for (const [, route] of this._routes) {
|
|
290
|
+
if (now <= route.expiresAt) {
|
|
291
|
+
totalRoutes++;
|
|
292
|
+
if (route.hopCount > maxHops) {
|
|
293
|
+
maxHops = route.hopCount;
|
|
294
|
+
}
|
|
295
|
+
hopSum += route.hopCount;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
280
298
|
|
|
281
299
|
return {
|
|
282
|
-
totalRoutes
|
|
300
|
+
totalRoutes,
|
|
283
301
|
directNeighbors: this._neighbors.size,
|
|
284
|
-
maxHops:
|
|
285
|
-
avgHops:
|
|
286
|
-
? hopCounts.reduce((a, b) => a + b, 0) / hopCounts.length
|
|
287
|
-
: 0
|
|
302
|
+
maxHops: totalRoutes > 0 ? maxHops : 0,
|
|
303
|
+
avgHops: totalRoutes > 0 ? hopSum / totalRoutes : 0
|
|
288
304
|
};
|
|
289
305
|
}
|
|
290
306
|
}
|