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.
Files changed (80) hide show
  1. package/README.md +288 -172
  2. package/docs/IOS-BACKGROUND-BLE.md +231 -0
  3. package/docs/OPTIMIZATION.md +183 -0
  4. package/docs/SPEC-v2.1.md +308 -0
  5. package/package.json +1 -1
  6. package/src/MeshNetwork.js +667 -465
  7. package/src/constants/index.js +1 -0
  8. package/src/crypto/AutoCrypto.js +90 -0
  9. package/src/crypto/CryptoProvider.js +99 -0
  10. package/src/crypto/index.js +15 -63
  11. package/src/crypto/providers/ExpoCryptoProvider.js +126 -0
  12. package/src/crypto/providers/QuickCryptoProvider.js +158 -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/AppStateManager.js +9 -1
  18. package/src/hooks/useMesh.js +47 -13
  19. package/src/hooks/useMessages.js +6 -4
  20. package/src/hooks/usePeers.js +13 -9
  21. package/src/index.js +23 -8
  22. package/src/mesh/dedup/BloomFilter.js +44 -57
  23. package/src/mesh/dedup/DedupManager.js +67 -10
  24. package/src/mesh/fragment/Assembler.js +5 -0
  25. package/src/mesh/fragment/Fragmenter.js +1 -1
  26. package/src/mesh/index.js +1 -1
  27. package/src/mesh/monitor/ConnectionQuality.js +433 -0
  28. package/src/mesh/monitor/NetworkMonitor.js +376 -320
  29. package/src/mesh/monitor/index.js +7 -3
  30. package/src/mesh/peer/Peer.js +5 -2
  31. package/src/mesh/peer/PeerManager.js +21 -4
  32. package/src/mesh/router/MessageRouter.js +38 -19
  33. package/src/mesh/router/RouteTable.js +24 -8
  34. package/src/mesh/store/StoreAndForwardManager.js +305 -296
  35. package/src/mesh/store/index.js +1 -1
  36. package/src/protocol/deserializer.js +9 -10
  37. package/src/protocol/header.js +13 -7
  38. package/src/protocol/message.js +15 -3
  39. package/src/protocol/serializer.js +7 -10
  40. package/src/protocol/validator.js +23 -5
  41. package/src/service/BatteryOptimizer.js +285 -278
  42. package/src/service/EmergencyManager.js +224 -214
  43. package/src/service/HandshakeManager.js +163 -13
  44. package/src/service/MeshService.js +72 -6
  45. package/src/service/SessionManager.js +79 -2
  46. package/src/service/audio/AudioManager.js +8 -2
  47. package/src/service/file/FileAssembler.js +106 -0
  48. package/src/service/file/FileChunker.js +79 -0
  49. package/src/service/file/FileManager.js +307 -0
  50. package/src/service/file/FileMessage.js +122 -0
  51. package/src/service/file/index.js +15 -0
  52. package/src/service/text/TextManager.js +21 -15
  53. package/src/service/text/broadcast/BroadcastManager.js +16 -0
  54. package/src/storage/MessageStore.js +55 -2
  55. package/src/transport/BLETransport.js +141 -10
  56. package/src/transport/MockTransport.js +1 -1
  57. package/src/transport/MultiTransport.js +330 -0
  58. package/src/transport/WiFiDirectTransport.js +296 -0
  59. package/src/transport/adapters/NodeBLEAdapter.js +34 -0
  60. package/src/transport/adapters/RNBLEAdapter.js +56 -1
  61. package/src/transport/index.js +6 -0
  62. package/src/utils/EventEmitter.js +6 -9
  63. package/src/utils/bytes.js +12 -10
  64. package/src/utils/compression.js +293 -291
  65. package/src/utils/encoding.js +33 -8
  66. package/src/crypto/aead.js +0 -189
  67. package/src/crypto/chacha20.js +0 -181
  68. package/src/crypto/hkdf.js +0 -187
  69. package/src/crypto/hmac.js +0 -143
  70. package/src/crypto/keys/KeyManager.js +0 -271
  71. package/src/crypto/keys/KeyPair.js +0 -216
  72. package/src/crypto/keys/SecureStorage.js +0 -219
  73. package/src/crypto/keys/index.js +0 -32
  74. package/src/crypto/noise/handshake.js +0 -410
  75. package/src/crypto/noise/index.js +0 -27
  76. package/src/crypto/noise/session.js +0 -253
  77. package/src/crypto/noise/state.js +0 -268
  78. package/src/crypto/poly1305.js +0 -113
  79. package/src/crypto/sha256.js +0 -240
  80. package/src/crypto/x25519.js +0 -154
@@ -8,5 +8,5 @@
8
8
  const StoreAndForwardManager = require('./StoreAndForwardManager');
9
9
 
10
10
  module.exports = {
11
- StoreAndForwardManager,
11
+ StoreAndForwardManager
12
12
  };
@@ -39,15 +39,14 @@ function deserializeHeader(data) {
39
39
  });
40
40
  }
41
41
 
42
- const headerData = data.slice(0, HEADER_SIZE);
43
- const view = new DataView(headerData.buffer, headerData.byteOffset, HEADER_SIZE);
42
+ const headerData = data.subarray(0, HEADER_SIZE);
43
+ const view = new DataView(data.buffer, data.byteOffset, HEADER_SIZE);
44
44
 
45
45
  // Extract checksum from bytes 44-47 (big-endian)
46
46
  const storedChecksum = view.getUint32(44, false);
47
47
 
48
- // Calculate checksum over bytes 0-43
49
- const checksumData = headerData.slice(0, 44);
50
- const calculatedChecksum = crc32(checksumData);
48
+ // Calculate checksum over bytes 0-43 (subarray = zero-copy view)
49
+ const calculatedChecksum = crc32(headerData.subarray(0, 44));
51
50
 
52
51
  // Verify checksum
53
52
  if (storedChecksum !== calculatedChecksum) {
@@ -65,7 +64,7 @@ function deserializeHeader(data) {
65
64
  hopCount: headerData[3],
66
65
  maxHops: headerData[4],
67
66
  // bytes 5-7 are reserved
68
- messageId: headerData.slice(8, 24),
67
+ messageId: new Uint8Array(headerData.buffer, headerData.byteOffset + 8, 16),
69
68
  timestamp: readUint64BE(view, 24),
70
69
  expiresAt: readUint64BE(view, 32),
71
70
  payloadLength: view.getUint16(40, false),
@@ -120,8 +119,8 @@ function deserialize(data) {
120
119
  });
121
120
  }
122
121
 
123
- // Extract payload
124
- const payload = data.slice(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
122
+ // Extract payload (subarray = zero-copy view)
123
+ const payload = data.subarray(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
125
124
 
126
125
  return new Message(header, payload);
127
126
  }
@@ -175,7 +174,7 @@ function deserializeBatch(data) {
175
174
  }
176
175
 
177
176
  // Extract and deserialize this message
178
- const messageData = data.slice(offset, offset + messageLength);
177
+ const messageData = data.subarray(offset, offset + messageLength);
179
178
 
180
179
  try {
181
180
  const message = deserialize(messageData);
@@ -228,7 +227,7 @@ function peekMessageId(data) {
228
227
  if (!(data instanceof Uint8Array) || data.length < 24) {
229
228
  return null;
230
229
  }
231
- return data.slice(8, 24);
230
+ return data.subarray(8, 24);
232
231
  }
233
232
 
234
233
  module.exports = {
@@ -16,6 +16,12 @@ const { randomBytes } = require('../utils/bytes');
16
16
  */
17
17
  const HEADER_SIZE = 48;
18
18
 
19
+ // Pre-computed hex lookup table (avoids Array.from().map().join() per call)
20
+ const HEX_TABLE = new Array(256);
21
+ for (let i = 0; i < 256; i++) {
22
+ HEX_TABLE[i] = (i < 16 ? '0' : '') + i.toString(16);
23
+ }
24
+
19
25
  /**
20
26
  * Message header class representing the 48-byte header structure.
21
27
  * @class MessageHeader
@@ -43,7 +49,6 @@ class MessageHeader {
43
49
  this.flags = options.flags ?? MESSAGE_FLAGS.NONE;
44
50
  this.hopCount = options.hopCount ?? 0;
45
51
  this.maxHops = options.maxHops ?? MESH_CONFIG.MAX_HOPS;
46
- this.reserved = new Uint8Array(3);
47
52
  this.messageId = options.messageId;
48
53
  this.timestamp = options.timestamp;
49
54
  this.expiresAt = options.expiresAt;
@@ -143,9 +148,8 @@ class MessageHeader {
143
148
  view.setUint16(40, header.payloadLength, false);
144
149
  buffer[42] = header.fragmentIndex;
145
150
  buffer[43] = header.fragmentTotal;
146
- // Calculate checksum over header without checksum field
147
- const checksumData = buffer.slice(0, 44);
148
- const checksum = crc32(checksumData);
151
+ // Calculate checksum over header without checksum field (subarray = zero-copy view)
152
+ const checksum = crc32(buffer.subarray(0, 44));
149
153
  view.setUint32(44, checksum, false);
150
154
  header.checksum = checksum;
151
155
 
@@ -204,9 +208,11 @@ function writeUint64BE(view, offset, value) {
204
208
  * @returns {string} Hex string
205
209
  */
206
210
  function bytesToHex(bytes) {
207
- return Array.from(bytes)
208
- .map(b => b.toString(16).padStart(2, '0'))
209
- .join('');
211
+ let hex = '';
212
+ for (let i = 0; i < bytes.length; i++) {
213
+ hex += HEX_TABLE[bytes[i]];
214
+ }
215
+ return hex;
210
216
  }
211
217
 
212
218
  module.exports = {
@@ -9,6 +9,18 @@ const { MessageHeader, HEADER_SIZE, generateUuid } = require('./header');
9
9
  const { MESSAGE_FLAGS, MESH_CONFIG } = require('../constants');
10
10
  const { MessageError } = require('../errors');
11
11
 
12
+ // Cached TextEncoder/TextDecoder singletons (avoids per-call allocation)
13
+ let _encoder = null;
14
+ let _decoder = null;
15
+ function _getEncoder() {
16
+ if (!_encoder) { _encoder = new TextEncoder(); }
17
+ return _encoder;
18
+ }
19
+ function _getDecoder() {
20
+ if (!_decoder) { _decoder = new TextDecoder(); }
21
+ return _decoder;
22
+ }
23
+
12
24
  /**
13
25
  * Message class representing a complete mesh network message.
14
26
  * @class Message
@@ -44,7 +56,7 @@ class Message {
44
56
 
45
57
  // Convert string payload to bytes
46
58
  if (typeof payload === 'string') {
47
- payload = new TextEncoder().encode(payload);
59
+ payload = _getEncoder().encode(payload);
48
60
  }
49
61
 
50
62
  if (!(payload instanceof Uint8Array)) {
@@ -99,7 +111,7 @@ class Message {
99
111
  });
100
112
  }
101
113
 
102
- const payload = data.slice(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
114
+ const payload = data.subarray(HEADER_SIZE, HEADER_SIZE + header.payloadLength);
103
115
 
104
116
  return new Message(header, payload);
105
117
  }
@@ -171,7 +183,7 @@ class Message {
171
183
  * @returns {string} Decoded payload content
172
184
  */
173
185
  getContent() {
174
- return new TextDecoder().decode(this.payload);
186
+ return _getDecoder().decode(this.payload);
175
187
  }
176
188
 
177
189
  /**
@@ -71,9 +71,8 @@ function serializeHeader(header) {
71
71
  // Byte 43: fragmentTotal
72
72
  buffer[43] = header.fragmentTotal ?? 1;
73
73
 
74
- // Bytes 44-47: checksum (calculated over bytes 0-43)
75
- const checksumData = buffer.slice(0, 44);
76
- const checksum = crc32(checksumData);
74
+ // Bytes 44-47: checksum (calculated over bytes 0-43, subarray = zero-copy view)
75
+ const checksum = crc32(buffer.subarray(0, 44));
77
76
  view.setUint32(44, checksum, false);
78
77
 
79
78
  return buffer;
@@ -110,7 +109,8 @@ function serialize(message) {
110
109
 
111
110
  // Convert string payload to bytes
112
111
  if (typeof payload === 'string') {
113
- payload = new TextEncoder().encode(payload);
112
+ if (!serialize._encoder) { serialize._encoder = new TextEncoder(); }
113
+ payload = serialize._encoder.encode(payload);
114
114
  }
115
115
 
116
116
  // Default to empty payload
@@ -122,13 +122,10 @@ function serialize(message) {
122
122
  throw MessageError.invalidFormat(null, { reason: 'Payload must be Uint8Array or string' });
123
123
  }
124
124
 
125
- // Update payloadLength in header
126
- const headerWithLength = {
127
- ...header,
128
- payloadLength: payload.length
129
- };
125
+ // Update payloadLength directly (avoids object spread allocation)
126
+ header.payloadLength = payload.length;
130
127
 
131
- const headerBytes = serializeHeader(headerWithLength);
128
+ const headerBytes = serializeHeader(header);
132
129
  const result = new Uint8Array(headerBytes.length + payload.length);
133
130
 
134
131
  result.set(headerBytes, 0);
@@ -15,6 +15,12 @@ const { PROTOCOL_VERSION, MESSAGE_TYPE, MESH_CONFIG } = require('../constants');
15
15
  */
16
16
  const VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));
17
17
 
18
+ /**
19
+ * Cached frozen result for valid validations to avoid repeated allocations.
20
+ * @type {{ valid: boolean, errors: string[] }}
21
+ */
22
+ const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
23
+
18
24
  /**
19
25
  * Validates a message header.
20
26
  *
@@ -102,8 +108,12 @@ function validateHeader(header) {
102
108
  errors.push(`Fragment index (${header.fragmentIndex}) >= total (${header.fragmentTotal})`);
103
109
  }
104
110
 
111
+ if (errors.length === 0) {
112
+ return VALID_RESULT;
113
+ }
114
+
105
115
  return {
106
- valid: errors.length === 0,
116
+ valid: false,
107
117
  errors
108
118
  };
109
119
  }
@@ -152,8 +162,12 @@ function validateMessage(message) {
152
162
  }
153
163
  }
154
164
 
165
+ if (errors.length === 0) {
166
+ return VALID_RESULT;
167
+ }
168
+
155
169
  return {
156
- valid: errors.length === 0,
170
+ valid: false,
157
171
  errors
158
172
  };
159
173
  }
@@ -171,7 +185,7 @@ function validateChecksum(headerBytes) {
171
185
 
172
186
  const view = new DataView(headerBytes.buffer, headerBytes.byteOffset, HEADER_SIZE);
173
187
  const storedChecksum = view.getUint32(44, false);
174
- const checksumData = headerBytes.slice(0, 44);
188
+ const checksumData = headerBytes.subarray(0, 44);
175
189
  const calculatedChecksum = crc32(checksumData);
176
190
 
177
191
  return {
@@ -242,7 +256,7 @@ function validateRawMessage(data) {
242
256
  }
243
257
 
244
258
  // Validate checksum
245
- const checksumResult = validateChecksum(data.slice(0, HEADER_SIZE));
259
+ const checksumResult = validateChecksum(data.subarray(0, HEADER_SIZE));
246
260
  if (!checksumResult.valid) {
247
261
  errors.push(
248
262
  `Checksum mismatch: expected 0x${checksumResult.expected.toString(16)}, ` +
@@ -259,8 +273,12 @@ function validateRawMessage(data) {
259
273
  errors.push(`Incomplete message: expected ${expectedTotal} bytes, got ${data.length}`);
260
274
  }
261
275
 
276
+ if (errors.length === 0) {
277
+ return VALID_RESULT;
278
+ }
279
+
262
280
  return {
263
- valid: errors.length === 0,
281
+ valid: false,
264
282
  errors
265
283
  };
266
284
  }