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.
Files changed (93) hide show
  1. package/README.md +2 -2
  2. package/docs/OPTIMIZATION.md +165 -52
  3. package/package.json +1 -1
  4. package/src/MeshNetwork.js +63 -53
  5. package/src/constants/audio.js +4 -4
  6. package/src/constants/ble.js +1 -1
  7. package/src/constants/crypto.js +1 -1
  8. package/src/constants/errors.js +2 -2
  9. package/src/constants/events.js +1 -1
  10. package/src/constants/protocol.js +2 -2
  11. package/src/crypto/AutoCrypto.js +16 -3
  12. package/src/crypto/CryptoProvider.js +17 -17
  13. package/src/crypto/providers/ExpoCryptoProvider.js +15 -9
  14. package/src/crypto/providers/QuickCryptoProvider.js +41 -12
  15. package/src/crypto/providers/TweetNaClProvider.js +10 -8
  16. package/src/errors/AudioError.js +2 -1
  17. package/src/errors/ConnectionError.js +2 -2
  18. package/src/errors/CryptoError.js +1 -1
  19. package/src/errors/HandshakeError.js +2 -2
  20. package/src/errors/MeshError.js +4 -4
  21. package/src/errors/MessageError.js +2 -2
  22. package/src/errors/ValidationError.js +3 -3
  23. package/src/expo/withBLEMesh.js +10 -10
  24. package/src/hooks/AppStateManager.js +11 -2
  25. package/src/hooks/useMesh.js +23 -10
  26. package/src/hooks/useMessages.js +17 -16
  27. package/src/hooks/usePeers.js +19 -14
  28. package/src/index.js +2 -2
  29. package/src/mesh/dedup/BloomFilter.js +45 -57
  30. package/src/mesh/dedup/DedupManager.js +36 -8
  31. package/src/mesh/dedup/MessageCache.js +3 -0
  32. package/src/mesh/fragment/Assembler.js +5 -4
  33. package/src/mesh/fragment/Fragmenter.js +3 -3
  34. package/src/mesh/monitor/ConnectionQuality.js +59 -25
  35. package/src/mesh/monitor/NetworkMonitor.js +80 -28
  36. package/src/mesh/peer/Peer.js +9 -11
  37. package/src/mesh/peer/PeerDiscovery.js +18 -19
  38. package/src/mesh/peer/PeerManager.js +29 -17
  39. package/src/mesh/router/MessageRouter.js +28 -20
  40. package/src/mesh/router/PathFinder.js +10 -13
  41. package/src/mesh/router/RouteTable.js +25 -14
  42. package/src/mesh/store/StoreAndForwardManager.js +32 -24
  43. package/src/protocol/deserializer.js +9 -10
  44. package/src/protocol/header.js +13 -7
  45. package/src/protocol/message.js +18 -14
  46. package/src/protocol/serializer.js +9 -12
  47. package/src/protocol/validator.js +29 -10
  48. package/src/service/BatteryOptimizer.js +22 -18
  49. package/src/service/EmergencyManager.js +18 -25
  50. package/src/service/HandshakeManager.js +112 -18
  51. package/src/service/MeshService.js +106 -22
  52. package/src/service/SessionManager.js +50 -13
  53. package/src/service/audio/AudioManager.js +80 -38
  54. package/src/service/audio/buffer/FrameBuffer.js +7 -8
  55. package/src/service/audio/buffer/JitterBuffer.js +1 -1
  56. package/src/service/audio/codec/LC3Codec.js +18 -19
  57. package/src/service/audio/codec/LC3Decoder.js +10 -10
  58. package/src/service/audio/codec/LC3Encoder.js +11 -9
  59. package/src/service/audio/session/AudioSession.js +14 -17
  60. package/src/service/audio/session/VoiceMessage.js +15 -22
  61. package/src/service/audio/transport/AudioFragmenter.js +17 -9
  62. package/src/service/audio/transport/AudioFramer.js +8 -12
  63. package/src/service/file/FileAssembler.js +4 -2
  64. package/src/service/file/FileChunker.js +1 -1
  65. package/src/service/file/FileManager.js +26 -20
  66. package/src/service/file/FileMessage.js +7 -12
  67. package/src/service/text/TextManager.js +75 -42
  68. package/src/service/text/broadcast/BroadcastManager.js +14 -17
  69. package/src/service/text/channel/Channel.js +10 -14
  70. package/src/service/text/channel/ChannelManager.js +10 -10
  71. package/src/service/text/message/TextMessage.js +12 -19
  72. package/src/service/text/message/TextSerializer.js +2 -2
  73. package/src/storage/AsyncStorageAdapter.js +17 -14
  74. package/src/storage/MemoryStorage.js +11 -8
  75. package/src/storage/MessageStore.js +77 -32
  76. package/src/storage/Storage.js +9 -9
  77. package/src/transport/BLETransport.js +27 -16
  78. package/src/transport/MockTransport.js +7 -2
  79. package/src/transport/MultiTransport.js +43 -11
  80. package/src/transport/Transport.js +9 -9
  81. package/src/transport/WiFiDirectTransport.js +26 -20
  82. package/src/transport/adapters/BLEAdapter.js +19 -19
  83. package/src/transport/adapters/NodeBLEAdapter.js +24 -23
  84. package/src/transport/adapters/RNBLEAdapter.js +14 -11
  85. package/src/utils/EventEmitter.js +15 -16
  86. package/src/utils/LRUCache.js +10 -4
  87. package/src/utils/RateLimiter.js +1 -1
  88. package/src/utils/bytes.js +12 -10
  89. package/src/utils/compression.js +10 -8
  90. package/src/utils/encoding.js +39 -8
  91. package/src/utils/retry.js +11 -13
  92. package/src/utils/time.js +9 -4
  93. package/src/utils/validation.js +1 -1
package/README.md CHANGED
@@ -558,7 +558,7 @@ react-native-ble-mesh/
558
558
  │ └── hooks/ # React hooks
559
559
  ├── docs/ # Guides & specs
560
560
  ├── app.plugin.js # Expo plugin entry
561
- └── __tests__/ # 432 tests, 0 failures ✅
561
+ └── __tests__/ # 433 tests, 0 failures ✅
562
562
  ```
563
563
 
564
564
  ---
@@ -579,7 +579,7 @@ react-native-ble-mesh/
579
579
  ## Testing
580
580
 
581
581
  ```bash
582
- npm test # Run all 432 tests
582
+ npm test # Run all 433 tests
583
583
  npm run test:coverage # With coverage report
584
584
  ```
585
585
 
@@ -1,70 +1,183 @@
1
- # Optimization & Technical Improvements
1
+ # Performance Optimization Guide
2
2
 
3
- ## Summary of Changes
3
+ ## v2.1.0 Performance Overhaul
4
4
 
5
- ### 🔴 Breaking: Crypto Module Removed
6
- The pure JavaScript cryptographic implementations (`src/crypto/`) have been **removed entirely**. This includes:
7
- - X25519 key exchange (pure JS BigInt — extremely slow)
8
- - ChaCha20-Poly1305 AEAD encryption
9
- - SHA-256 hashing
10
- - HMAC-SHA256
11
- - HKDF key derivation
12
- - Noise Protocol XX handshake
5
+ Version 2.1.0 is a comprehensive performance optimization release targeting React Native speed, reduced GC pressure, and elimination of memory leaks. All changes are non-breaking.
13
6
 
14
- **Why:** Pure JS BigInt field arithmetic for X25519 is orders of magnitude slower than native implementations. On mobile devices, this caused:
15
- - ~100ms+ per key exchange (vs ~1ms with native)
16
- - Battery drain from CPU-intensive crypto
17
- - UI thread blocking on Hermes/JSC
7
+ ### Key Principles Applied
8
+
9
+ 1. **Zero-copy where possible** Use `Uint8Array.subarray()` instead of `slice()` for read-only views
10
+ 2. **Cache singletons** `TextEncoder`, `TextDecoder`, crypto providers, hex lookup tables
11
+ 3. **Avoid allocation in hot paths** — Inline computations, reuse buffers, pre-compute constants
12
+ 4. **O(1) over O(n)** — Circular buffers, running sums, pre-computed Sets
13
+ 5. **Clean up resources** — Clear timers, remove event listeners, bound map growth
14
+
15
+ ---
16
+
17
+ ## Hot Path Optimizations
18
+
19
+ ### Message Processing Pipeline
20
+
21
+ Every message flows through: transport → deserialize → dedup → route → serialize → transport. Each stage was optimized:
22
+
23
+ | Stage | Before | After | Improvement |
24
+ |-------|--------|-------|-------------|
25
+ | Deserialize header | 3x `slice()` copies | 0 copies (`subarray()`) | ~3x fewer allocations |
26
+ | Deserialize payload | 1x `slice()` copy | 0 copies (`subarray()`) | Zero-copy payload |
27
+ | CRC32 checksum | `slice(0, 44)` copy | `subarray(0, 44)` view | Zero-copy |
28
+ | BloomFilter check | `new TextEncoder()` + array alloc | Cached encoder, inlined positions | 3 fewer allocations/msg |
29
+ | BloomFilter fill ratio | O(n) bit scan | O(1) running counter | Constant time |
30
+ | Serialize header | `slice(0, 44)` for CRC | `subarray(0, 44)` view | Zero-copy |
31
+ | Hex conversion | `Array.from().map().join()` | Pre-computed lookup table | ~5x faster |
32
+ | UUID generation | 16 temp strings + join | Hex table + concatenation | ~3x faster |
33
+ | Encrypt/Decrypt nonce | `new Uint8Array(24)` per call | Pre-allocated per session | 0 allocations |
34
+
35
+ ### TextEncoder/TextDecoder Caching
36
+
37
+ Before v2.1.0, `new TextEncoder()` was called on every:
38
+ - Message validation (byte length check)
39
+ - Message encoding (broadcast, private, channel)
40
+ - BloomFilter dedup check (string → bytes)
41
+ - File chunk encoding (per-chunk in loop!)
42
+ - Protocol serialization (string payloads)
43
+ - Read receipt handling (per-receipt in loop)
44
+
45
+ Now: **One singleton per module**, created once at import time.
46
+
47
+ ### Crypto Provider Caching
48
+
49
+ - `AutoCrypto.detectProvider()` — Now caches the singleton result
50
+ - `QuickCryptoProvider` — Caches `require('tweetnacl')` result instead of calling per encrypt/decrypt
51
+ - DER header buffers for X25519 — Hoisted to module level (were re-created from hex per handshake)
52
+
53
+ ---
54
+
55
+ ## Memory Leak Fixes
56
+
57
+ ### Timer Leaks
58
+
59
+ | Location | Issue | Fix |
60
+ |----------|-------|-----|
61
+ | `MeshNetwork.sendFile()` | Per-chunk timeout timers never cleared on success | `clearTimeout()` in both success/error paths |
62
+ | `DedupManager._resetBloomFilter()` | Grace period timer leaked on repeated resets | Store timer ID, clear on reset/destroy |
63
+
64
+ ### Event Listener Leaks
65
+
66
+ | Location | Issue | Fix |
67
+ |----------|-------|-----|
68
+ | `MultiTransport._wireTransport()` | Handlers never removed across start/stop | Store references, remove in `stop()` |
69
+ | `useMesh.js` initialize | Listeners stacked on re-init | Store in refs, remove old before adding new |
70
+ | `AppStateManager` | `.bind()` per initialize, old subscription not removed | Bind once in constructor, remove old sub |
71
+
72
+ ### Unbounded Growth
73
+
74
+ | Location | Issue | Fix |
75
+ |----------|-------|-----|
76
+ | `NetworkMonitor._pendingMessages` | Undelivered messages never removed | Cleanup entries older than `nodeTimeoutMs` |
77
+ | `ConnectionQuality._peers` | Timer runs with 0 peers | Stop timer when last peer removed |
78
+ | `BLETransport.disconnectFromPeer()` | Write queue/writing maps not cleaned | Added cleanup in disconnect |
79
+
80
+ ---
81
+
82
+ ## Data Structure Improvements
83
+
84
+ ### Circular Buffers (O(n) → O(1))
85
+
86
+ `Array.shift()` is O(n) because it re-indexes all elements. For sliding windows that shift on every sample, this was a significant cost.
87
+
88
+ **Replaced with circular buffers:**
89
+ - `NetworkMonitor._latencies` → `Float64Array` ring buffer with running sum
90
+ - `ConnectionQuality._rssiSamples` → `Float64Array` ring buffer with running sum
91
+ - `ConnectionQuality._latencySamples` → `Float64Array` ring buffer with running sum
18
92
 
19
- **What to use instead:**
20
- - [`tweetnacl`](https://www.npmjs.com/package/tweetnacl) — Lightweight, audited, works everywhere (recommended)
21
- - [`libsodium-wrappers`](https://www.npmjs.com/package/libsodium-wrappers) — Full-featured, WASM-based
22
- - [`react-native-quick-crypto`](https://www.npmjs.com/package/react-native-quick-crypto) — Native crypto for RN (fastest)
93
+ Average computation is now O(1) via running sum instead of O(n) `reduce()`.
23
94
 
24
- Consumers should implement their own encryption layer using these established libraries.
95
+ ### Pre-Computed Sets (O(n) O(1))
25
96
 
26
- ### 🟢 Bug Fixes
97
+ `Object.values(ENUM).includes(value)` creates an array and does linear scan on every call.
27
98
 
28
- 1. **MeshNetwork restart** Fixed crash when calling `start()` after `stop()`. The service was trying to re-initialize (state check failed). Now skips initialization if already initialized.
99
+ **Replaced with module-level `Set`s:**
100
+ - `Peer.setConnectionState()` — `CONNECTION_STATE_SET.has(state)`
101
+ - `BatteryOptimizer.setMode()` — `BATTERY_MODE_SET.has(mode)`
29
102
 
30
- 2. **MockTransport auto-ID** — `MockTransport` now auto-generates a `localPeerId` if none provided, preventing "localPeerId required" errors when linking transports.
103
+ ### Direct Map Iteration
31
104
 
32
- 3. **Error message clarity** — All error classes (MeshError, ValidationError, ConnectionError, etc.) now prefix messages with the error class name (e.g., `"ValidationError: Invalid type"`), making error identification easier in catch blocks and logs.
105
+ `Array.from(map.values()).filter(...)` creates two arrays. Direct iteration creates one:
33
106
 
34
- ### 🟡 Performance Optimizations
107
+ ```js
108
+ // Before (2 arrays)
109
+ getConnectedPeers() { return this.getAllPeers().filter(p => p.isConnected()); }
35
110
 
36
- 4. **BLE connection timeout cleanup** — Fixed timer leak in `BLETransport.connectToPeer()`. The timeout timer was never cleared on successful connection, leaking memory. Now properly clears the timer when connection succeeds.
111
+ // After (1 array)
112
+ getConnectedPeers() {
113
+ const result = [];
114
+ for (const peer of this._peers.values()) {
115
+ if (peer.isConnected()) result.push(peer);
116
+ }
117
+ return result;
118
+ }
119
+ ```
37
120
 
38
- ### 🧪 Test Improvements
121
+ Applied to: `PeerManager.getConnectedPeers()`, `getSecuredPeers()`, `getDirectPeers()`
39
122
 
40
- - **Fixed all 10 previously failing tests** (was 396 total, 10 failing → 344 total, 0 failing)
41
- - **Added new test suites:**
42
- - `__tests__/transport/BLETransport.test.js` — Lifecycle, scanning, connections, broadcast, timeout handling
43
- - `__tests__/transport/MockTransport.test.js` — Linking, message passing, peer simulation
44
- - `__tests__/mesh/MeshNetwork.unit.test.js` — Config merging, validation, lifecycle, restart
45
- - `__tests__/service/BatteryOptimizer.test.js` — Mode switching, battery levels, cleanup
46
- - `__tests__/service/MeshService.test.js` — Full lifecycle, identity, peers, messaging
47
- - `__tests__/platform/ios.test.js` — Background mode, MTU fragmentation, state restoration
48
- - `__tests__/platform/android.test.js` — Permissions, MTU (23/512), Doze mode, memory pressure, BloomFilter FP rate
123
+ ---
49
124
 
50
- ### 📱 Platform Compatibility Verified
125
+ ## React Native Hook Optimizations
51
126
 
52
- **iOS:**
53
- - BLE background mode behavior tested
54
- - MTU 185 (BLE 4.2+) fragmentation verified
55
- - Battery optimizer integration tested
56
- - Store-and-forward for state restoration
127
+ ### usePeers — Eliminated Double Re-render
57
128
 
58
- **Android:**
59
- - BLE permission denial handled gracefully
60
- - MTU 23 (BLE 4.0) and 512 (BLE 5.0) fragmentation tested
61
- - Doze mode with low-power settings verified
62
- - LRU cache respects size limits under memory pressure
63
- - BloomFilter false positive rate verified (<20% at reasonable capacity)
129
+ Before: Every peer event called both `setPeers()` and `setLastUpdate(Date.now())`, causing two re-renders (React may batch, but not always across async boundaries).
64
130
 
65
- ## Remaining Recommendations
131
+ After: `lastUpdate` is a `useRef` (no re-render). Added shallow comparison to skip `setPeers()` when peers haven't actually changed.
132
+
133
+ Also: `getPeer()` now uses a `useMemo` Map for O(1) lookup instead of O(n) `Array.find()`.
134
+
135
+ ### useMessages — Reduced Array Copies
136
+
137
+ Before: 3 array copies per incoming message when over `maxMessages` (`[msg, ...prev]` + `slice(maxMessages)` + `slice(0, maxMessages)`).
138
+
139
+ After: 1 array copy + in-place truncation via `updated.length = maxMessages`.
140
+
141
+ ---
142
+
143
+ ## Storage Optimization
144
+
145
+ ### MessageStore Payload Encoding
146
+
147
+ Before: `Array.from(Uint8Array)` converted each byte to a boxed JS Number — **8x memory bloat** (1 byte → 8 bytes for Number object + array overhead).
148
+
149
+ After: Base64 encoding. A 10KB payload uses ~13.3KB as base64 string (1.33x) instead of ~80KB as Number array (8x).
150
+
151
+ ---
152
+
153
+ ## Compression Optimization
154
+
155
+ ### LZ4 Hash Table Reuse
156
+
157
+ Before: `new Int32Array(4096)` (16KB allocation) on every `_lz4Compress()` call.
158
+
159
+ After: Pre-allocated in constructor, reused with `.fill(-1)` reset per call.
160
+
161
+ ---
162
+
163
+ ## WiFi Direct Base64 Fix
164
+
165
+ ### O(n^2) → O(n) String Building
166
+
167
+ Before: Character-by-character `binary += String.fromCharCode(bytes[i])` — O(n^2) due to string immutability.
168
+
169
+ After: Chunk-based `String.fromCharCode.apply(null, bytes.subarray(i, i+8192))` with final `join()` — O(n).
170
+
171
+ For a 1MB file transfer, this eliminates ~1 million intermediate string allocations.
172
+
173
+ ---
174
+
175
+ ## Historical Changes (v2.0.0)
176
+
177
+ ### Crypto Module Removed
178
+ The pure JavaScript cryptographic implementations (`src/crypto/`) were removed in v2.0.0. Pure JS BigInt field arithmetic for X25519 was orders of magnitude slower than native:
179
+ - ~100ms+ per key exchange (vs ~1ms with native)
180
+ - Battery drain from CPU-intensive crypto
181
+ - UI thread blocking on Hermes/JSC
66
182
 
67
- 1. **Add `tweetnacl` as peer dependency** for consumers who need encryption
68
- 2. **Consider TypeScript migration** — current JS codebase with JSDoc is good but TS would catch more errors
69
- 3. **Add integration tests with real BLE** — current tests use MockTransport; consider Detox/Appium for device testing
70
- 4. **Publish to npm** with proper semver (this is a breaking change → v2.0.0)
183
+ Replaced by the pluggable provider system: `tweetnacl`, `react-native-quick-crypto`, or `expo-crypto`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-ble-mesh",
3
- "version": "2.0.0",
3
+ "version": "2.1.2",
4
4
  "description": "React Native Bluetooth Low Energy (BLE) mesh networking library with end-to-end encryption, offline messaging, peer-to-peer communication, and Noise Protocol security for iOS and Android",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -23,9 +23,14 @@ const { ValidationError, MeshError } = require('./errors');
23
23
  const { FileManager } = require('./service/file');
24
24
  const { SERVICE_STATE, EVENTS } = require('./constants');
25
25
 
26
+ /** @private Cached TextEncoder singleton – avoids per-call allocation */
27
+ const _encoder = new TextEncoder();
28
+ /** @private Cached TextDecoder singleton – avoids per-call allocation */
29
+ const _decoder = new TextDecoder();
30
+
26
31
  /**
27
32
  * Default MeshNetwork configuration
28
- * @constant {Object}
33
+ * @constant {any}
29
34
  */
30
35
  const DEFAULT_CONFIG = Object.freeze({
31
36
  /** Display name for this node */
@@ -83,21 +88,21 @@ class MeshNetwork extends EventEmitter {
83
88
  * @param {Object} [config.encryption] - Encryption settings
84
89
  * @param {Object} [config.routing] - Routing settings
85
90
  * @param {Object} [config.compression] - Compression settings
86
- * @param {Object} [config.storeAndForward] - Store and forward settings
91
+ * @param {any} [config.storeAndForward] - Store and forward settings
87
92
  */
88
93
  constructor(config = {}) {
89
94
  super();
90
95
 
91
96
  /**
92
97
  * Configuration
93
- * @type {Object}
98
+ * @type {any}
94
99
  * @private
95
100
  */
96
101
  this._config = this._mergeConfig(DEFAULT_CONFIG, config);
97
102
 
98
103
  /**
99
104
  * Underlying MeshService
100
- * @type {MeshService}
105
+ * @type {InstanceType<typeof MeshService>}
101
106
  * @private
102
107
  */
103
108
  this._service = new MeshService({
@@ -106,7 +111,7 @@ class MeshNetwork extends EventEmitter {
106
111
 
107
112
  /**
108
113
  * Transport layer
109
- * @type {Transport|null}
114
+ * @type {any}
110
115
  * @private
111
116
  */
112
117
  this._transport = null;
@@ -175,7 +180,7 @@ class MeshNetwork extends EventEmitter {
175
180
 
176
181
  /**
177
182
  * Channel manager reference
178
- * @type {Object|null}
183
+ * @type {any}
179
184
  * @private
180
185
  */
181
186
  this._channels = null;
@@ -197,7 +202,7 @@ class MeshNetwork extends EventEmitter {
197
202
 
198
203
  /**
199
204
  * Starts the mesh network.
200
- * @param {Object} [transport] - Optional custom transport
205
+ * @param {any} [transport] - Optional custom transport
201
206
  * @returns {Promise<void>}
202
207
  */
203
208
  async start(transport) {
@@ -211,7 +216,7 @@ class MeshNetwork extends EventEmitter {
211
216
  const { RNBLEAdapter } = require('./transport/adapters');
212
217
  const adapter = new RNBLEAdapter();
213
218
  this._transport = new BLETransport(adapter);
214
- } catch (e) {
219
+ } catch (/** @type {any} */ e) {
215
220
  throw new MeshError(
216
221
  'No transport provided and BLE adapter not available. ' +
217
222
  'Either pass a transport to start(), install react-native-ble-plx for BLE, ' +
@@ -247,15 +252,15 @@ class MeshNetwork extends EventEmitter {
247
252
  }
248
253
 
249
254
  // Setup file transfer events
250
- this._fileManager.on('sendProgress', (info) => this.emit('fileSendProgress', info));
251
- this._fileManager.on('receiveProgress', (info) => this.emit('fileReceiveProgress', info));
252
- this._fileManager.on('fileReceived', (info) => this.emit('fileReceived', info));
253
- this._fileManager.on('transferFailed', (info) => this.emit('fileTransferFailed', info));
254
- this._fileManager.on('transferCancelled', (info) => this.emit('fileTransferCancelled', info));
255
+ this._fileManager.on('sendProgress', (/** @type {any} */ info) => this.emit('fileSendProgress', info));
256
+ this._fileManager.on('receiveProgress', (/** @type {any} */ info) => this.emit('fileReceiveProgress', info));
257
+ this._fileManager.on('fileReceived', (/** @type {any} */ info) => this.emit('fileReceived', info));
258
+ this._fileManager.on('transferFailed', (/** @type {any} */ info) => this.emit('fileTransferFailed', info));
259
+ this._fileManager.on('transferCancelled', (/** @type {any} */ info) => this.emit('fileTransferCancelled', info));
255
260
 
256
261
  // Start connection quality monitoring
257
262
  this._connectionQuality.start();
258
- this._connectionQuality.on('qualityChanged', (quality) => {
263
+ this._connectionQuality.on('qualityChanged', (/** @type {any} */ quality) => {
259
264
  this.emit('connectionQualityChanged', quality);
260
265
  });
261
266
 
@@ -347,9 +352,9 @@ class MeshNetwork extends EventEmitter {
347
352
  // If peer is offline and store-forward is enabled, cache the message
348
353
  if (this._storeForward && this._isPeerOffline(peerId)) {
349
354
  const payload = this._encodeMessage(text);
350
- await this._storeForward.cacheForOfflinePeer(peerId, payload, {
355
+ await this._storeForward.cacheForOfflinePeer(peerId, payload, /** @type {any} */ ({
351
356
  needsEncryption: true
352
- });
357
+ }));
353
358
  this.emit('messageCached', { peerId, text });
354
359
  return 'cached';
355
360
  }
@@ -405,7 +410,7 @@ class MeshNetwork extends EventEmitter {
405
410
 
406
411
  /**
407
412
  * Gets list of joined channels.
408
- * @returns {Object[]} Channels
413
+ * @returns {any[]} Channels
409
414
  */
410
415
  getChannels() {
411
416
  return this._service.getChannels();
@@ -417,7 +422,7 @@ class MeshNetwork extends EventEmitter {
417
422
 
418
423
  /**
419
424
  * Gets all known peers.
420
- * @returns {Object[]} Array of peers
425
+ * @returns {any[]} Array of peers
421
426
  */
422
427
  getPeers() {
423
428
  return this._service.getPeers();
@@ -425,7 +430,7 @@ class MeshNetwork extends EventEmitter {
425
430
 
426
431
  /**
427
432
  * Gets connected peers.
428
- * @returns {Object[]} Connected peers
433
+ * @returns {any[]} Connected peers
429
434
  */
430
435
  getConnectedPeers() {
431
436
  return this._service.getConnectedPeers();
@@ -433,7 +438,7 @@ class MeshNetwork extends EventEmitter {
433
438
 
434
439
  /**
435
440
  * Gets peers with secure sessions.
436
- * @returns {Object[]} Secured peers
441
+ * @returns {any[]} Secured peers
437
442
  */
438
443
  getSecuredPeers() {
439
444
  return this._service.getSecuredPeers();
@@ -463,7 +468,7 @@ class MeshNetwork extends EventEmitter {
463
468
 
464
469
  /**
465
470
  * Gets network health metrics.
466
- * @returns {Object} Health report
471
+ * @returns {any} Health report
467
472
  */
468
473
  getNetworkHealth() {
469
474
  return this._monitor.generateHealthReport();
@@ -472,7 +477,7 @@ class MeshNetwork extends EventEmitter {
472
477
  /**
473
478
  * Gets detailed health for a specific peer.
474
479
  * @param {string} peerId - Peer ID
475
- * @returns {Object|null} Node health
480
+ * @returns {any} Node health
476
481
  */
477
482
  getPeerHealth(peerId) {
478
483
  return this._monitor.getNodeHealth(peerId);
@@ -557,7 +562,7 @@ class MeshNetwork extends EventEmitter {
557
562
  * @param {Uint8Array} fileInfo.data - File data
558
563
  * @param {string} fileInfo.name - File name
559
564
  * @param {string} [fileInfo.mimeType] - MIME type
560
- * @returns {Object} Transfer info with id and event emitter
565
+ * @returns {Promise<any>} Transfer info with id and event emitter
561
566
  */
562
567
  async sendFile(peerId, fileInfo) {
563
568
  this._validateRunning();
@@ -567,17 +572,19 @@ class MeshNetwork extends EventEmitter {
567
572
  throw new ValidationError('File must have data and name', 'E800');
568
573
  }
569
574
 
575
+ /** @type {any} */
570
576
  const transfer = this._fileManager.prepareSend(peerId, fileInfo);
571
577
 
572
578
  // Send offer (JSON is OK for metadata, but use binary type marker)
573
579
  const offerJson = JSON.stringify(transfer.offer);
574
- const offerBytes = new TextEncoder().encode(offerJson);
580
+ const offerBytes = _encoder.encode(offerJson);
575
581
  const offerPayload = new Uint8Array(1 + offerBytes.length);
576
582
  offerPayload[0] = 0x01; // Binary type marker for OFFER
577
583
  offerPayload.set(offerBytes, 1);
578
584
  await this._service._sendRaw(peerId, offerPayload);
579
585
 
580
586
  // Send chunks sequentially using binary protocol
587
+ const transferIdBytes = _encoder.encode(transfer.id);
581
588
  for (let i = 0; i < transfer.chunks.length; i++) {
582
589
  // Check if still running (handles app backgrounding)
583
590
  if (this._state !== 'running') {
@@ -588,7 +595,6 @@ class MeshNetwork extends EventEmitter {
588
595
  const chunk = transfer.chunks[i];
589
596
 
590
597
  // Binary format: [type(1)] [transferIdLen(1)] [transferId(N)] [index(2)] [data(M)]
591
- const transferIdBytes = new TextEncoder().encode(transfer.id);
592
598
  const header = new Uint8Array(1 + 1 + transferIdBytes.length + 2);
593
599
  let offset = 0;
594
600
  header[offset++] = 0x02; // Binary type marker for CHUNK
@@ -605,13 +611,16 @@ class MeshNetwork extends EventEmitter {
605
611
 
606
612
  // Add per-chunk timeout (10 seconds)
607
613
  const sendPromise = this._service._sendRaw(peerId, payload);
608
- const timeoutPromise = new Promise((_, reject) =>
609
- setTimeout(() => reject(new Error('Chunk send timeout')), 10000)
610
- );
614
+ let timeoutId;
615
+ const timeoutPromise = new Promise((/** @type {any} */ _, /** @type {any} */ reject) => {
616
+ timeoutId = setTimeout(() => reject(new Error('Chunk send timeout')), 10000);
617
+ });
611
618
 
612
619
  try {
613
620
  await Promise.race([sendPromise, timeoutPromise]);
614
- } catch (err) {
621
+ clearTimeout(timeoutId);
622
+ } catch (/** @type {any} */ err) {
623
+ clearTimeout(timeoutId);
615
624
  this._fileManager.cancelTransfer(transfer.id);
616
625
  this.emit('fileTransferFailed', {
617
626
  id: transfer.id, name: fileInfo.name, error: err.message
@@ -627,7 +636,7 @@ class MeshNetwork extends EventEmitter {
627
636
 
628
637
  /**
629
638
  * Gets active file transfers
630
- * @returns {Object} { outgoing: [], incoming: [] }
639
+ * @returns {any} { outgoing: [], incoming: [] }
631
640
  */
632
641
  getActiveTransfers() {
633
642
  return this._fileManager.getActiveTransfers();
@@ -648,7 +657,7 @@ class MeshNetwork extends EventEmitter {
648
657
  /**
649
658
  * Gets connection quality for a specific peer.
650
659
  * @param {string} peerId - Peer ID
651
- * @returns {Object|null} Quality report
660
+ * @returns {any} Quality report
652
661
  */
653
662
  getConnectionQuality(peerId) {
654
663
  return this._connectionQuality.getQuality(peerId);
@@ -656,7 +665,7 @@ class MeshNetwork extends EventEmitter {
656
665
 
657
666
  /**
658
667
  * Gets connection quality for all peers.
659
- * @returns {Object[]} Array of quality reports
668
+ * @returns {any[]} Array of quality reports
660
669
  */
661
670
  getAllConnectionQuality() {
662
671
  return this._connectionQuality.getAllQuality();
@@ -668,7 +677,7 @@ class MeshNetwork extends EventEmitter {
668
677
 
669
678
  /**
670
679
  * Gets the network status.
671
- * @returns {Object} Status
680
+ * @returns {any} Status
672
681
  */
673
682
  getStatus() {
674
683
  const identity = this._state === 'running' ? this._service.getIdentity() : null;
@@ -685,7 +694,7 @@ class MeshNetwork extends EventEmitter {
685
694
 
686
695
  /**
687
696
  * Gets the identity information.
688
- * @returns {Object} Identity
697
+ * @returns {any} Identity
689
698
  */
690
699
  getIdentity() {
691
700
  return this._service.getIdentity();
@@ -706,9 +715,9 @@ class MeshNetwork extends EventEmitter {
706
715
 
707
716
  /**
708
717
  * Merges configuration with defaults.
709
- * @param {Object} defaults - Default config
710
- * @param {Object} custom - Custom config
711
- * @returns {Object} Merged config
718
+ * @param {any} defaults - Default config
719
+ * @param {any} custom - Custom config
720
+ * @returns {any} Merged config
712
721
  * @private
713
722
  */
714
723
  _mergeConfig(defaults, custom) {
@@ -730,12 +739,12 @@ class MeshNetwork extends EventEmitter {
730
739
  */
731
740
  _setupEventForwarding() {
732
741
  // Forward service events with PRD-style naming
733
- this._service.on(EVENTS.PEER_DISCOVERED, (peer) => {
742
+ this._service.on(EVENTS.PEER_DISCOVERED, (/** @type {any} */ peer) => {
734
743
  this._monitor.trackPeerDiscovered(peer.id);
735
744
  this.emit('peerDiscovered', peer);
736
745
  });
737
746
 
738
- this._service.on(EVENTS.PEER_CONNECTED, (peer) => {
747
+ this._service.on(EVENTS.PEER_CONNECTED, (/** @type {any} */ peer) => {
739
748
  if (peer.rssi) {
740
749
  this._connectionQuality.recordRssi(peer.id, peer.rssi);
741
750
  }
@@ -746,13 +755,13 @@ class MeshNetwork extends EventEmitter {
746
755
  }
747
756
  });
748
757
 
749
- this._service.on(EVENTS.PEER_DISCONNECTED, (peer) => {
758
+ this._service.on(EVENTS.PEER_DISCONNECTED, (/** @type {any} */ peer) => {
750
759
  this._monitor.trackPeerDisconnected(peer.id);
751
760
  this._connectionQuality.removePeer(peer.id);
752
761
  this.emit('peerDisconnected', peer);
753
762
  });
754
763
 
755
- this._service.on('message', (message) => {
764
+ this._service.on('message', (/** @type {any} */ message) => {
756
765
  this._monitor.trackMessageReceived(message.senderId);
757
766
  this.emit('messageReceived', {
758
767
  from: message.senderId,
@@ -762,7 +771,7 @@ class MeshNetwork extends EventEmitter {
762
771
  });
763
772
  });
764
773
 
765
- this._service.on('private-message', (message) => {
774
+ this._service.on('private-message', (/** @type {any} */ message) => {
766
775
  this.emit('directMessage', {
767
776
  from: message.senderId,
768
777
  text: message.content,
@@ -770,7 +779,7 @@ class MeshNetwork extends EventEmitter {
770
779
  });
771
780
  });
772
781
 
773
- this._service.on('channel-message', (message) => {
782
+ this._service.on('channel-message', (/** @type {any} */ message) => {
774
783
  this.emit('channelMessage', {
775
784
  channel: message.channelId,
776
785
  from: message.senderId,
@@ -779,22 +788,22 @@ class MeshNetwork extends EventEmitter {
779
788
  });
780
789
  });
781
790
 
782
- this._service.on('message-delivered', (info) => {
791
+ this._service.on('message-delivered', (/** @type {any} */ info) => {
783
792
  this._monitor.trackMessageDelivered(info.messageId);
784
793
  this.emit('messageDelivered', info);
785
794
  });
786
795
 
787
- this._service.on('error', (error) => {
796
+ this._service.on('error', (/** @type {any} */ error) => {
788
797
  this.emit('error', error);
789
798
  });
790
799
 
791
800
  // Forward monitor events
792
- this._monitor.on('health-changed', (info) => {
801
+ this._monitor.on('health-changed', (/** @type {any} */ info) => {
793
802
  this.emit('networkHealthChanged', info);
794
803
  });
795
804
 
796
805
  // Forward emergency events
797
- this._emergencyManager.on('panic-wipe-completed', (result) => {
806
+ this._emergencyManager.on('panic-wipe-completed', (/** @type {any} */ result) => {
798
807
  this.emit('dataWiped', result);
799
808
  });
800
809
  }
@@ -815,17 +824,18 @@ class MeshNetwork extends EventEmitter {
815
824
  async _deliverCachedMessages(peerId) {
816
825
  if (!this._storeForward) { return; }
817
826
 
818
- const sendFn = async (payload) => {
827
+ const sendFn = async (/** @type {any} */ payload) => {
819
828
  // Re-encrypt and send via the proper encrypted channel
820
829
  try {
821
- const text = new TextDecoder().decode(payload);
830
+ const text = _decoder.decode(payload);
822
831
  await this._service.sendPrivateMessage(peerId, text);
823
- } catch (e) {
832
+ } catch (/** @type {any} */ e) {
824
833
  // Fallback to raw send if encryption fails
825
834
  await this._service._sendRaw(peerId, payload);
826
835
  }
827
836
  };
828
837
 
838
+ /** @type {any} */
829
839
  const result = await this._storeForward.deliverCachedMessages(peerId, sendFn);
830
840
 
831
841
  if (result.delivered > 0) {
@@ -849,7 +859,7 @@ class MeshNetwork extends EventEmitter {
849
859
  // Clear store and forward cache
850
860
  if (this._storeForward) {
851
861
  this._emergencyManager.registerClearer(async () => {
852
- this._storeForward.clear();
862
+ this._storeForward?.clear();
853
863
  });
854
864
  }
855
865
 
@@ -892,7 +902,7 @@ class MeshNetwork extends EventEmitter {
892
902
  * @private
893
903
  */
894
904
  _encodeMessage(text) {
895
- return new TextEncoder().encode(text);
905
+ return _encoder.encode(text);
896
906
  }
897
907
 
898
908
  /**
@@ -936,7 +946,7 @@ class MeshNetwork extends EventEmitter {
936
946
  }
937
947
 
938
948
  // Check size limit (UTF-8 encoded)
939
- const byteLength = new TextEncoder().encode(text).length;
949
+ const byteLength = _encoder.encode(text).length;
940
950
  if (byteLength > MeshNetwork.MAX_MESSAGE_SIZE) {
941
951
  throw ValidationError.outOfRange('text', byteLength, {
942
952
  min: 1,