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,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Expo config plugin for react-native-ble-mesh
5
+ * @module expo/withBLEMesh
6
+ *
7
+ * Automatically configures BLE permissions and background modes
8
+ * for iOS and Android when used with Expo.
9
+ *
10
+ * Usage in app.json:
11
+ * {
12
+ * "expo": {
13
+ * "plugins": [
14
+ * ["react-native-ble-mesh", {
15
+ * "bluetoothAlwaysPermission": "Chat with nearby devices via Bluetooth",
16
+ * "backgroundModes": ["bluetooth-central", "bluetooth-peripheral"]
17
+ * }]
18
+ * ]
19
+ * }
20
+ * }
21
+ */
22
+
23
+ /**
24
+ * Default configuration
25
+ * @constant {Object}
26
+ */
27
+ const DEFAULT_OPTIONS = {
28
+ /** iOS NSBluetoothAlwaysUsageDescription */
29
+ bluetoothAlwaysPermission: 'This app uses Bluetooth to communicate with nearby devices',
30
+ /** iOS UIBackgroundModes */
31
+ backgroundModes: ['bluetooth-central', 'bluetooth-peripheral'],
32
+ /** Android permissions */
33
+ androidPermissions: [
34
+ 'android.permission.BLUETOOTH_SCAN',
35
+ 'android.permission.BLUETOOTH_CONNECT',
36
+ 'android.permission.BLUETOOTH_ADVERTISE',
37
+ 'android.permission.ACCESS_FINE_LOCATION'
38
+ ]
39
+ };
40
+
41
+ /**
42
+ * Modifies the iOS Info.plist for BLE permissions.
43
+ * @param {Object} config - Expo config
44
+ * @param {Object} options - Plugin options
45
+ * @returns {Object} Modified config
46
+ */
47
+ function withBLEMeshIOS(config, options) {
48
+ const opts = { ...DEFAULT_OPTIONS, ...options };
49
+
50
+ if (!config.ios) { config.ios = {}; }
51
+ if (!config.ios.infoPlist) { config.ios.infoPlist = {}; }
52
+
53
+ // Add Bluetooth usage description
54
+ config.ios.infoPlist.NSBluetoothAlwaysUsageDescription = opts.bluetoothAlwaysPermission;
55
+ config.ios.infoPlist.NSBluetoothPeripheralUsageDescription = opts.bluetoothAlwaysPermission;
56
+
57
+ // Add background modes
58
+ const existingModes = config.ios.infoPlist.UIBackgroundModes || [];
59
+ const newModes = new Set([...existingModes, ...opts.backgroundModes]);
60
+ config.ios.infoPlist.UIBackgroundModes = Array.from(newModes);
61
+
62
+ return config;
63
+ }
64
+
65
+ /**
66
+ * Modifies the Android manifest for BLE permissions.
67
+ * @param {Object} config - Expo config
68
+ * @param {Object} options - Plugin options
69
+ * @returns {Object} Modified config
70
+ */
71
+ function withBLEMeshAndroid(config, options) {
72
+ const opts = { ...DEFAULT_OPTIONS, ...options };
73
+
74
+ if (!config.android) { config.android = {}; }
75
+ if (!config.android.permissions) { config.android.permissions = []; }
76
+
77
+ // Add BLE permissions (avoid duplicates)
78
+ const existingPerms = new Set(config.android.permissions);
79
+ for (const perm of opts.androidPermissions) {
80
+ existingPerms.add(perm);
81
+ }
82
+ config.android.permissions = Array.from(existingPerms);
83
+
84
+ return config;
85
+ }
86
+
87
+ /**
88
+ * Main Expo config plugin.
89
+ * @param {Object} config - Expo config
90
+ * @param {Object} [options={}] - Plugin options
91
+ * @returns {Object} Modified config
92
+ */
93
+ function withBLEMesh(config, options = {}) {
94
+ config = withBLEMeshIOS(config, options);
95
+ config = withBLEMeshAndroid(config, options);
96
+ return config;
97
+ }
98
+
99
+ module.exports = withBLEMesh;
100
+ module.exports.withBLEMeshIOS = withBLEMeshIOS;
101
+ module.exports.withBLEMeshAndroid = withBLEMeshAndroid;
102
+ module.exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
@@ -46,6 +46,7 @@ function useMesh(config = {}) {
46
46
 
47
47
  // Create mesh instance ref (persists across renders)
48
48
  const meshRef = useRef(null);
49
+ const mountedRef = useRef(true);
49
50
 
50
51
  // State
51
52
  const [state, setState] = useState('uninitialized');
@@ -64,6 +65,7 @@ function useMesh(config = {}) {
64
65
  // Initialize mesh
65
66
  const initialize = useCallback(async (transport) => {
66
67
  try {
68
+ if (!mountedRef.current) { return; }
67
69
  setState('initializing');
68
70
  setError(null);
69
71
 
@@ -71,11 +73,15 @@ function useMesh(config = {}) {
71
73
 
72
74
  // Setup state change listener
73
75
  mesh.on('state-changed', ({ newState }) => {
74
- setState(newState);
76
+ if (mountedRef.current) {
77
+ setState(newState);
78
+ }
75
79
  });
76
80
 
77
81
  mesh.on('error', (err) => {
78
- setError(err);
82
+ if (mountedRef.current) {
83
+ setError(err);
84
+ }
79
85
  });
80
86
 
81
87
  // Initialize with storage
@@ -90,8 +96,10 @@ function useMesh(config = {}) {
90
96
 
91
97
  return mesh;
92
98
  } catch (err) {
93
- setState('error');
94
- setError(err);
99
+ if (mountedRef.current) {
100
+ setState('error');
101
+ setError(err);
102
+ }
95
103
  throw err;
96
104
  }
97
105
  }, [getMesh, config.storage]);
@@ -102,7 +110,9 @@ function useMesh(config = {}) {
102
110
  try {
103
111
  await mesh.start(transport);
104
112
  } catch (err) {
105
- setError(err);
113
+ if (mountedRef.current) {
114
+ setError(err);
115
+ }
106
116
  throw err;
107
117
  }
108
118
  }, [getMesh]);
@@ -114,7 +124,9 @@ function useMesh(config = {}) {
114
124
  try {
115
125
  await mesh.stop();
116
126
  } catch (err) {
117
- setError(err);
127
+ if (mountedRef.current) {
128
+ setError(err);
129
+ }
118
130
  }
119
131
  }
120
132
  }, []);
@@ -129,16 +141,25 @@ function useMesh(config = {}) {
129
141
  // Ignore destroy errors
130
142
  }
131
143
  meshRef.current = null;
132
- setState('destroyed');
144
+ if (mountedRef.current) {
145
+ setState('destroyed');
146
+ }
133
147
  }
134
148
  }, []);
135
149
 
136
150
  // Cleanup on unmount
137
151
  useEffect(() => {
138
152
  return () => {
153
+ // Immediately mark as unmounted to prevent state updates
154
+ mountedRef.current = false;
155
+
139
156
  if (meshRef.current) {
140
- meshRef.current.destroy().catch(() => {});
141
- meshRef.current = null;
157
+ const mesh = meshRef.current;
158
+ meshRef.current = null; // Null ref immediately to prevent new operations
159
+
160
+ mesh.destroy().catch(() => {
161
+ // Ignore cleanup errors
162
+ });
142
163
  }
143
164
  };
144
165
  }, []);
@@ -114,6 +114,8 @@ function useMessages(mesh, options = {}) {
114
114
  mesh.off('broadcast-received', handleBroadcast);
115
115
  mesh.off('private-message-received', handlePrivate);
116
116
  mesh.off('channel-message-received', handleChannel);
117
+ // Clear dedup Set on unmount
118
+ messageIdRef.current.clear();
117
119
  };
118
120
  }, [mesh, addMessage]);
119
121
 
package/src/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * @fileoverview BLE Mesh Network Library
3
3
  * @module rn-ble-mesh
4
4
  * @description Production-ready BLE Mesh Network with Noise Protocol security
5
- *
6
- * This is the definitive React Native library for BitChat-compatible
5
+ *
6
+ * This is the definitive React Native library for BitChat-compatible
7
7
  * decentralized mesh networking.
8
8
  */
9
9
 
@@ -16,9 +16,7 @@ const { MeshNetwork, BATTERY_MODE, PANIC_TRIGGER, HEALTH_STATUS } = require('./M
16
16
  const {
17
17
  MeshService,
18
18
  EmergencyManager,
19
- BatteryOptimizer,
20
- SessionManager,
21
- HandshakeManager,
19
+ BatteryOptimizer
22
20
  } = require('./service');
23
21
 
24
22
  // Constants
@@ -27,7 +25,7 @@ const constants = require('./constants');
27
25
  // Errors
28
26
  const errors = require('./errors');
29
27
 
30
- // Crypto
28
+ // Crypto — pluggable provider system (tweetnacl / quick-crypto / expo-crypto)
31
29
  const crypto = require('./crypto');
32
30
 
33
31
  // Protocol
@@ -51,6 +49,10 @@ const audio = require('./service/audio');
51
49
  // Text (from service module)
52
50
  const text = require('./service/text');
53
51
 
52
+ // File transfer
53
+ const file = require('./service/file');
54
+ const { FileManager, FileChunker, FileAssembler, FileMessage, FILE_MESSAGE_TYPE, FILE_TRANSFER_STATE } = file;
55
+
54
56
  // React Native hooks
55
57
  const hooks = require('./hooks');
56
58
 
@@ -133,7 +135,7 @@ const { useMesh, usePeers, useMessages, AppStateManager } = hooks;
133
135
 
134
136
  // New PRD-specified components
135
137
  const { StoreAndForwardManager } = mesh;
136
- const { NetworkMonitor } = mesh;
138
+ const { NetworkMonitor, ConnectionQuality, QUALITY_LEVEL } = mesh;
137
139
  const { MessageCompressor, compress, decompress } = utils;
138
140
 
139
141
  module.exports = {
@@ -156,6 +158,10 @@ module.exports = {
156
158
  // Store and Forward
157
159
  StoreAndForwardManager,
158
160
 
161
+ // Connection Quality
162
+ ConnectionQuality,
163
+ QUALITY_LEVEL,
164
+
159
165
  // Compression
160
166
  MessageCompressor,
161
167
  compress,
@@ -186,7 +192,7 @@ module.exports = {
186
192
  // Errors
187
193
  ...errors,
188
194
 
189
- // Crypto primitives and Noise Protocol
195
+ // Crypto provider system
190
196
  crypto,
191
197
 
192
198
  // Protocol serialization
@@ -229,6 +235,15 @@ module.exports = {
229
235
  useMessages,
230
236
  AppStateManager,
231
237
 
238
+ // File transfer
239
+ FileManager,
240
+ FileChunker,
241
+ FileAssembler,
242
+ FileMessage,
243
+ FILE_MESSAGE_TYPE,
244
+ FILE_TRANSFER_STATE,
245
+ file,
246
+
232
247
  // Hooks module
233
248
  hooks
234
249
  };
@@ -55,6 +55,13 @@ class DedupManager {
55
55
  */
56
56
  this._cache = new MessageCache(config.cacheSize);
57
57
 
58
+ /**
59
+ * Old Bloom filter kept during grace period after reset
60
+ * @type {BloomFilter|null}
61
+ * @private
62
+ */
63
+ this._oldBloomFilter = null;
64
+
58
65
  /**
59
66
  * Auto-reset threshold for Bloom filter
60
67
  * @type {number}
@@ -85,20 +92,25 @@ class DedupManager {
85
92
  isDuplicate(messageId) {
86
93
  this._stats.checks++;
87
94
 
88
- // Quick check with Bloom filter
89
- if (!this._bloomFilter.mightContain(messageId)) {
90
- return false;
91
- }
92
- this._stats.bloomPositives++;
93
-
94
- // Confirm with exact cache lookup (handles false positives)
95
+ // Check cache first (most accurate)
95
96
  if (this._cache.has(messageId)) {
96
97
  this._stats.cacheHits++;
97
98
  this._stats.duplicates++;
98
99
  return true;
99
100
  }
100
101
 
101
- // Bloom filter false positive
102
+ // Check current bloom filter
103
+ if (this._bloomFilter.mightContain(messageId)) {
104
+ this._stats.bloomPositives++;
105
+ return true;
106
+ }
107
+
108
+ // Check old bloom filter if in grace period
109
+ if (this._oldBloomFilter && this._oldBloomFilter.mightContain(messageId)) {
110
+ this._stats.bloomPositives++;
111
+ return true;
112
+ }
113
+
102
114
  return false;
103
115
  }
104
116
 
@@ -135,14 +147,28 @@ class DedupManager {
135
147
  * @private
136
148
  */
137
149
  _resetBloomFilter() {
138
- this._bloomFilter.clear();
150
+ // Create a new bloom filter instead of clearing the old one
151
+ // Keep the old filter active for checking during transition
152
+ const oldFilter = this._bloomFilter;
153
+ this._bloomFilter = new BloomFilter(
154
+ oldFilter.size || MESH_CONFIG.BLOOM_FILTER_SIZE,
155
+ oldFilter.hashCount || MESH_CONFIG.BLOOM_HASH_COUNT
156
+ );
139
157
  this._stats.resets++;
140
158
 
141
- // Re-add all cached entries to Bloom filter
159
+ // Re-add all cached entries to new filter
142
160
  const entries = this._cache.getAll();
143
161
  for (const messageId of entries) {
144
162
  this._bloomFilter.add(messageId);
145
163
  }
164
+
165
+ // Keep old filter for a grace period by checking both
166
+ this._oldBloomFilter = oldFilter;
167
+
168
+ // Clear old filter after grace period
169
+ setTimeout(() => {
170
+ this._oldBloomFilter = null;
171
+ }, 60000); // 1 minute grace
146
172
  }
147
173
 
148
174
  /**
@@ -73,6 +73,11 @@ class PendingFragmentSet {
73
73
  * @returns {Uint8Array} Complete assembled payload
74
74
  */
75
75
  assemble() {
76
+ const MAX_ASSEMBLY_SIZE = 500 * 1024; // 500KB max for mesh messages
77
+ if (this.totalPayloadLength > MAX_ASSEMBLY_SIZE) {
78
+ throw new Error(`Fragment assembly too large: ${this.totalPayloadLength} bytes exceeds ${MAX_ASSEMBLY_SIZE} byte limit`);
79
+ }
80
+
76
81
  const result = new Uint8Array(this.totalPayloadLength);
77
82
  let offset = 0;
78
83
 
package/src/mesh/index.js CHANGED
@@ -18,5 +18,5 @@ module.exports = {
18
18
  ...peer,
19
19
  ...router,
20
20
  ...store,
21
- ...monitor,
21
+ ...monitor
22
22
  };