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.
- package/README.md +2 -2
- package/docs/OPTIMIZATION.md +165 -52
- package/package.json +1 -1
- package/src/MeshNetwork.js +63 -53
- package/src/constants/audio.js +4 -4
- package/src/constants/ble.js +1 -1
- package/src/constants/crypto.js +1 -1
- package/src/constants/errors.js +2 -2
- package/src/constants/events.js +1 -1
- package/src/constants/protocol.js +2 -2
- package/src/crypto/AutoCrypto.js +16 -3
- package/src/crypto/CryptoProvider.js +17 -17
- package/src/crypto/providers/ExpoCryptoProvider.js +15 -9
- package/src/crypto/providers/QuickCryptoProvider.js +41 -12
- package/src/crypto/providers/TweetNaClProvider.js +10 -8
- package/src/errors/AudioError.js +2 -1
- package/src/errors/ConnectionError.js +2 -2
- package/src/errors/CryptoError.js +1 -1
- package/src/errors/HandshakeError.js +2 -2
- package/src/errors/MeshError.js +4 -4
- package/src/errors/MessageError.js +2 -2
- package/src/errors/ValidationError.js +3 -3
- package/src/expo/withBLEMesh.js +10 -10
- package/src/hooks/AppStateManager.js +11 -2
- package/src/hooks/useMesh.js +23 -10
- package/src/hooks/useMessages.js +17 -16
- package/src/hooks/usePeers.js +19 -14
- package/src/index.js +2 -2
- package/src/mesh/dedup/BloomFilter.js +45 -57
- package/src/mesh/dedup/DedupManager.js +36 -8
- package/src/mesh/dedup/MessageCache.js +3 -0
- package/src/mesh/fragment/Assembler.js +5 -4
- package/src/mesh/fragment/Fragmenter.js +3 -3
- package/src/mesh/monitor/ConnectionQuality.js +59 -25
- package/src/mesh/monitor/NetworkMonitor.js +80 -28
- package/src/mesh/peer/Peer.js +9 -11
- package/src/mesh/peer/PeerDiscovery.js +18 -19
- package/src/mesh/peer/PeerManager.js +29 -17
- package/src/mesh/router/MessageRouter.js +28 -20
- package/src/mesh/router/PathFinder.js +10 -13
- package/src/mesh/router/RouteTable.js +25 -14
- package/src/mesh/store/StoreAndForwardManager.js +32 -24
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +18 -14
- package/src/protocol/serializer.js +9 -12
- package/src/protocol/validator.js +29 -10
- package/src/service/BatteryOptimizer.js +22 -18
- package/src/service/EmergencyManager.js +18 -25
- package/src/service/HandshakeManager.js +112 -18
- package/src/service/MeshService.js +106 -22
- package/src/service/SessionManager.js +50 -13
- package/src/service/audio/AudioManager.js +80 -38
- package/src/service/audio/buffer/FrameBuffer.js +7 -8
- package/src/service/audio/buffer/JitterBuffer.js +1 -1
- package/src/service/audio/codec/LC3Codec.js +18 -19
- package/src/service/audio/codec/LC3Decoder.js +10 -10
- package/src/service/audio/codec/LC3Encoder.js +11 -9
- package/src/service/audio/session/AudioSession.js +14 -17
- package/src/service/audio/session/VoiceMessage.js +15 -22
- package/src/service/audio/transport/AudioFragmenter.js +17 -9
- package/src/service/audio/transport/AudioFramer.js +8 -12
- package/src/service/file/FileAssembler.js +4 -2
- package/src/service/file/FileChunker.js +1 -1
- package/src/service/file/FileManager.js +26 -20
- package/src/service/file/FileMessage.js +7 -12
- package/src/service/text/TextManager.js +75 -42
- package/src/service/text/broadcast/BroadcastManager.js +14 -17
- package/src/service/text/channel/Channel.js +10 -14
- package/src/service/text/channel/ChannelManager.js +10 -10
- package/src/service/text/message/TextMessage.js +12 -19
- package/src/service/text/message/TextSerializer.js +2 -2
- package/src/storage/AsyncStorageAdapter.js +17 -14
- package/src/storage/MemoryStorage.js +11 -8
- package/src/storage/MessageStore.js +77 -32
- package/src/storage/Storage.js +9 -9
- package/src/transport/BLETransport.js +27 -16
- package/src/transport/MockTransport.js +7 -2
- package/src/transport/MultiTransport.js +43 -11
- package/src/transport/Transport.js +9 -9
- package/src/transport/WiFiDirectTransport.js +26 -20
- package/src/transport/adapters/BLEAdapter.js +19 -19
- package/src/transport/adapters/NodeBLEAdapter.js +24 -23
- package/src/transport/adapters/RNBLEAdapter.js +14 -11
- package/src/utils/EventEmitter.js +15 -16
- package/src/utils/LRUCache.js +10 -4
- package/src/utils/RateLimiter.js +1 -1
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +10 -8
- package/src/utils/encoding.js +39 -8
- package/src/utils/retry.js +11 -13
- package/src/utils/time.js +9 -4
- package/src/utils/validation.js +1 -1
|
@@ -18,9 +18,9 @@ const { ValidationError, ConnectionError } = require('../../errors');
|
|
|
18
18
|
class PeerManager extends EventEmitter {
|
|
19
19
|
/**
|
|
20
20
|
* Creates a new PeerManager
|
|
21
|
-
* @param {
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* @param {any} [options] - Configuration options
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
24
|
*/
|
|
25
25
|
constructor(options = {}) {
|
|
26
26
|
super();
|
|
@@ -39,7 +39,7 @@ class PeerManager extends EventEmitter {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Peers by ID
|
|
42
|
-
* @type {Map<string,
|
|
42
|
+
* @type {Map<string, any>}
|
|
43
43
|
* @private
|
|
44
44
|
*/
|
|
45
45
|
this._peers = new Map();
|
|
@@ -54,8 +54,8 @@ class PeerManager extends EventEmitter {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Adds or updates a peer
|
|
57
|
-
* @param {
|
|
58
|
-
* @returns {
|
|
57
|
+
* @param {any} info - Peer information
|
|
58
|
+
* @returns {any} The added or updated peer
|
|
59
59
|
*/
|
|
60
60
|
addPeer(info) {
|
|
61
61
|
if (!info || typeof info.id !== 'string') {
|
|
@@ -90,7 +90,7 @@ class PeerManager extends EventEmitter {
|
|
|
90
90
|
/**
|
|
91
91
|
* Gets a peer by ID
|
|
92
92
|
* @param {string} id - Peer ID
|
|
93
|
-
* @returns {
|
|
93
|
+
* @returns {any} The peer or undefined
|
|
94
94
|
*/
|
|
95
95
|
getPeer(id) {
|
|
96
96
|
return this._peers.get(id);
|
|
@@ -98,7 +98,7 @@ class PeerManager extends EventEmitter {
|
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* Gets all peers
|
|
101
|
-
* @returns {
|
|
101
|
+
* @returns {any[]} Array of all peers
|
|
102
102
|
*/
|
|
103
103
|
getAllPeers() {
|
|
104
104
|
return Array.from(this._peers.values());
|
|
@@ -106,33 +106,45 @@ class PeerManager extends EventEmitter {
|
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Gets connected peers
|
|
109
|
-
* @returns {
|
|
109
|
+
* @returns {any[]} 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
|
/**
|
|
116
120
|
* Gets peers with secure sessions
|
|
117
|
-
* @returns {
|
|
121
|
+
* @returns {any[]} 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
|
/**
|
|
124
132
|
* Gets directly connected peers
|
|
125
|
-
* @returns {
|
|
133
|
+
* @returns {any[]} 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
|
/**
|
|
132
144
|
* Updates a peer's connection state
|
|
133
145
|
* @param {string} id - Peer ID
|
|
134
146
|
* @param {string} state - New connection state
|
|
135
|
-
* @returns {
|
|
147
|
+
* @returns {any} Updated peer or undefined
|
|
136
148
|
*/
|
|
137
149
|
updateConnectionState(id, state) {
|
|
138
150
|
const peer = this._peers.get(id);
|
|
@@ -154,7 +166,7 @@ class PeerManager extends EventEmitter {
|
|
|
154
166
|
/**
|
|
155
167
|
* Marks a peer as secured
|
|
156
168
|
* @param {string} id - Peer ID
|
|
157
|
-
* @returns {
|
|
169
|
+
* @returns {any} Updated peer or undefined
|
|
158
170
|
*/
|
|
159
171
|
markSecured(id) {
|
|
160
172
|
const peer = this._peers.get(id);
|
|
@@ -225,7 +237,7 @@ class PeerManager extends EventEmitter {
|
|
|
225
237
|
/**
|
|
226
238
|
* Cleans up stale peers
|
|
227
239
|
* @param {number} [maxAge] - Maximum age in ms, defaults to peerTimeout
|
|
228
|
-
* @returns {
|
|
240
|
+
* @returns {any[]} Array of removed peers
|
|
229
241
|
*/
|
|
230
242
|
cleanup(maxAge = this.peerTimeout) {
|
|
231
243
|
const removed = [];
|
|
@@ -12,18 +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 }, (/** @type {any} */ _, /** @type {number} */ 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
|
-
|
|
26
|
-
|
|
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]]}`;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -34,8 +42,8 @@ function generateUUID() {
|
|
|
34
42
|
class MessageRouter extends EventEmitter {
|
|
35
43
|
/**
|
|
36
44
|
* Creates a new MessageRouter
|
|
37
|
-
* @param {
|
|
38
|
-
*
|
|
45
|
+
* @param {any} options - Configuration options
|
|
46
|
+
*
|
|
39
47
|
*/
|
|
40
48
|
constructor(options = {}) {
|
|
41
49
|
super();
|
|
@@ -69,7 +77,7 @@ class MessageRouter extends EventEmitter {
|
|
|
69
77
|
|
|
70
78
|
/**
|
|
71
79
|
* Statistics
|
|
72
|
-
* @type {
|
|
80
|
+
* @type {any}
|
|
73
81
|
* @private
|
|
74
82
|
*/
|
|
75
83
|
this._stats = {
|
|
@@ -126,9 +134,9 @@ class MessageRouter extends EventEmitter {
|
|
|
126
134
|
|
|
127
135
|
/**
|
|
128
136
|
* Processes an incoming message
|
|
129
|
-
* @param {
|
|
137
|
+
* @param {any} message - Message object
|
|
130
138
|
* @param {string} sourcePeerId - Peer ID that sent the message
|
|
131
|
-
* @returns {
|
|
139
|
+
* @returns {any} Processed message or null if dropped
|
|
132
140
|
*/
|
|
133
141
|
processIncoming(message, sourcePeerId) {
|
|
134
142
|
this._stats.messagesReceived++;
|
|
@@ -188,7 +196,7 @@ class MessageRouter extends EventEmitter {
|
|
|
188
196
|
|
|
189
197
|
/**
|
|
190
198
|
* Relays a message to other peers
|
|
191
|
-
* @param {
|
|
199
|
+
* @param {any} message - Message to relay
|
|
192
200
|
* @param {string} excludePeerId - Peer to exclude from relay
|
|
193
201
|
* @private
|
|
194
202
|
*/
|
|
@@ -206,7 +214,7 @@ class MessageRouter extends EventEmitter {
|
|
|
206
214
|
try {
|
|
207
215
|
sendFn(relayedMessage);
|
|
208
216
|
this._stats.messagesRelayed++;
|
|
209
|
-
} catch (err) {
|
|
217
|
+
} catch (/** @type {any} */ err) {
|
|
210
218
|
this.emit(EVENTS.ERROR, err);
|
|
211
219
|
}
|
|
212
220
|
}
|
|
@@ -222,7 +230,7 @@ class MessageRouter extends EventEmitter {
|
|
|
222
230
|
|
|
223
231
|
/**
|
|
224
232
|
* Gets relay targets for a message
|
|
225
|
-
* @param {
|
|
233
|
+
* @param {any} message - Message
|
|
226
234
|
* @param {string} excludePeerId - Peer to exclude
|
|
227
235
|
* @returns {string[]} Array of peer IDs
|
|
228
236
|
* @private
|
|
@@ -230,7 +238,7 @@ class MessageRouter extends EventEmitter {
|
|
|
230
238
|
_getRelayTargets(message, excludePeerId) {
|
|
231
239
|
if (message.flags & MESSAGE_FLAGS.IS_BROADCAST) {
|
|
232
240
|
// Broadcast: send to all except source
|
|
233
|
-
return Array.from(this._peers.keys()).filter(id => id !== excludePeerId);
|
|
241
|
+
return Array.from(this._peers.keys()).filter((/** @type {string} */ id) => id !== excludePeerId);
|
|
234
242
|
}
|
|
235
243
|
|
|
236
244
|
// Unicast: find route to recipient
|
|
@@ -240,7 +248,7 @@ class MessageRouter extends EventEmitter {
|
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
// No known route - use limited flooding (max 3 peers, prefer recently active)
|
|
243
|
-
const allPeers = Array.from(this._peers.keys()).filter(id => id !== excludePeerId);
|
|
251
|
+
const allPeers = Array.from(this._peers.keys()).filter((/** @type {string} */ id) => id !== excludePeerId);
|
|
244
252
|
// Limit flood scope to prevent network storms
|
|
245
253
|
const maxFloodPeers = Math.min(3, allPeers.length);
|
|
246
254
|
return allPeers.slice(0, maxFloodPeers);
|
|
@@ -248,7 +256,7 @@ class MessageRouter extends EventEmitter {
|
|
|
248
256
|
|
|
249
257
|
/**
|
|
250
258
|
* Sends a message
|
|
251
|
-
* @param {
|
|
259
|
+
* @param {any} options - Send options
|
|
252
260
|
* @returns {string} Message ID
|
|
253
261
|
*/
|
|
254
262
|
send(options) {
|
|
@@ -288,7 +296,7 @@ class MessageRouter extends EventEmitter {
|
|
|
288
296
|
try {
|
|
289
297
|
sendFn(message);
|
|
290
298
|
anySent = true;
|
|
291
|
-
} catch (err) {
|
|
299
|
+
} catch (/** @type {any} */ err) {
|
|
292
300
|
this.emit(EVENTS.ERROR, err);
|
|
293
301
|
}
|
|
294
302
|
}
|
|
@@ -306,7 +314,7 @@ class MessageRouter extends EventEmitter {
|
|
|
306
314
|
|
|
307
315
|
/**
|
|
308
316
|
* Broadcasts a message to all peers
|
|
309
|
-
* @param {
|
|
317
|
+
* @param {any} options - Broadcast options
|
|
310
318
|
* @returns {string} Message ID
|
|
311
319
|
*/
|
|
312
320
|
broadcast(options) {
|
|
@@ -319,7 +327,7 @@ class MessageRouter extends EventEmitter {
|
|
|
319
327
|
|
|
320
328
|
/**
|
|
321
329
|
* Gets router statistics
|
|
322
|
-
* @returns {
|
|
330
|
+
* @returns {any} Statistics
|
|
323
331
|
*/
|
|
324
332
|
getStats() {
|
|
325
333
|
return {
|
|
@@ -28,10 +28,7 @@ const { ValidationError } = require('../../errors');
|
|
|
28
28
|
class PathFinder extends EventEmitter {
|
|
29
29
|
/**
|
|
30
30
|
* Creates a new PathFinder
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {string} options.localPeerId - Local peer ID
|
|
33
|
-
* @param {Object} options.routeTable - RouteTable instance
|
|
34
|
-
* @param {number} [options.discoveryTimeout] - Route discovery timeout
|
|
31
|
+
* @param {any} options - Configuration options *
|
|
35
32
|
*/
|
|
36
33
|
constructor(options = {}) {
|
|
37
34
|
super();
|
|
@@ -51,7 +48,7 @@ class PathFinder extends EventEmitter {
|
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
50
|
* Route table reference
|
|
54
|
-
* @type {
|
|
51
|
+
* @type {any}
|
|
55
52
|
*/
|
|
56
53
|
this.routeTable = options.routeTable;
|
|
57
54
|
|
|
@@ -63,7 +60,7 @@ class PathFinder extends EventEmitter {
|
|
|
63
60
|
|
|
64
61
|
/**
|
|
65
62
|
* Pending route requests
|
|
66
|
-
* @type {Map<string,
|
|
63
|
+
* @type {Map<string, any>}
|
|
67
64
|
* @private
|
|
68
65
|
*/
|
|
69
66
|
this._pendingRequests = new Map();
|
|
@@ -77,7 +74,7 @@ class PathFinder extends EventEmitter {
|
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
76
|
* Statistics
|
|
80
|
-
* @type {
|
|
77
|
+
* @type {any}
|
|
81
78
|
* @private
|
|
82
79
|
*/
|
|
83
80
|
this._stats = {
|
|
@@ -93,7 +90,7 @@ class PathFinder extends EventEmitter {
|
|
|
93
90
|
/**
|
|
94
91
|
* Finds a route to a destination
|
|
95
92
|
* @param {string} destination - Target peer ID
|
|
96
|
-
* @returns {Promise<
|
|
93
|
+
* @returns {Promise<any>} Route or null if not found
|
|
97
94
|
*/
|
|
98
95
|
async findRoute(destination) {
|
|
99
96
|
if (!destination || typeof destination !== 'string') {
|
|
@@ -113,7 +110,7 @@ class PathFinder extends EventEmitter {
|
|
|
113
110
|
/**
|
|
114
111
|
* Initiates route discovery for a destination
|
|
115
112
|
* @param {string} destination - Target peer ID
|
|
116
|
-
* @returns {Promise<
|
|
113
|
+
* @returns {Promise<any>} Discovered route or null
|
|
117
114
|
* @private
|
|
118
115
|
*/
|
|
119
116
|
_initiateDiscovery(destination) {
|
|
@@ -164,9 +161,9 @@ class PathFinder extends EventEmitter {
|
|
|
164
161
|
|
|
165
162
|
/**
|
|
166
163
|
* Processes an incoming route request
|
|
167
|
-
* @param {
|
|
164
|
+
* @param {any} request - Route request data
|
|
168
165
|
* @param {string} sourcePeerId - Source peer ID
|
|
169
|
-
* @returns {
|
|
166
|
+
* @returns {any} Reply to send or null
|
|
170
167
|
*/
|
|
171
168
|
processRouteRequest(request, sourcePeerId) {
|
|
172
169
|
this._stats.requestsReceived++;
|
|
@@ -219,7 +216,7 @@ class PathFinder extends EventEmitter {
|
|
|
219
216
|
|
|
220
217
|
/**
|
|
221
218
|
* Processes an incoming route reply
|
|
222
|
-
* @param {
|
|
219
|
+
* @param {any} reply - Route reply data
|
|
223
220
|
* @param {string} sourcePeerId - Source peer ID
|
|
224
221
|
*/
|
|
225
222
|
processRouteReply(reply, sourcePeerId) {
|
|
@@ -295,7 +292,7 @@ class PathFinder extends EventEmitter {
|
|
|
295
292
|
|
|
296
293
|
/**
|
|
297
294
|
* Gets path finder statistics
|
|
298
|
-
* @returns {
|
|
295
|
+
* @returns {any} Statistics
|
|
299
296
|
*/
|
|
300
297
|
getStats() {
|
|
301
298
|
return {
|
|
@@ -26,9 +26,9 @@ const { ValidationError } = require('../../errors');
|
|
|
26
26
|
class RouteTable {
|
|
27
27
|
/**
|
|
28
28
|
* Creates a new RouteTable
|
|
29
|
-
* @param {
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* @param {any} [options] - Configuration options
|
|
30
|
+
*
|
|
31
|
+
*
|
|
32
32
|
*/
|
|
33
33
|
constructor(options = {}) {
|
|
34
34
|
/**
|
|
@@ -45,7 +45,7 @@ class RouteTable {
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Routes by destination peer ID
|
|
48
|
-
* @type {Map<string,
|
|
48
|
+
* @type {Map<string, any>}
|
|
49
49
|
* @private
|
|
50
50
|
*/
|
|
51
51
|
this._routes = new Map();
|
|
@@ -130,7 +130,7 @@ class RouteTable {
|
|
|
130
130
|
/**
|
|
131
131
|
* Gets a route to a destination
|
|
132
132
|
* @param {string} destination - Destination peer ID
|
|
133
|
-
* @returns {
|
|
133
|
+
* @returns {any} Route or undefined
|
|
134
134
|
*/
|
|
135
135
|
getRoute(destination) {
|
|
136
136
|
const route = this._routes.get(destination);
|
|
@@ -197,7 +197,7 @@ class RouteTable {
|
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Gets all valid routes
|
|
200
|
-
* @returns {
|
|
200
|
+
* @returns {any[]} Array of routes
|
|
201
201
|
*/
|
|
202
202
|
getAllRoutes() {
|
|
203
203
|
const now = Date.now();
|
|
@@ -244,6 +244,7 @@ class RouteTable {
|
|
|
244
244
|
* @private
|
|
245
245
|
*/
|
|
246
246
|
_evictOldestRoute() {
|
|
247
|
+
/** @type {string|null} */
|
|
247
248
|
let oldest = null;
|
|
248
249
|
let oldestTime = Infinity;
|
|
249
250
|
|
|
@@ -278,19 +279,29 @@ class RouteTable {
|
|
|
278
279
|
|
|
279
280
|
/**
|
|
280
281
|
* Gets routing table statistics
|
|
281
|
-
* @returns {
|
|
282
|
+
* @returns {any} Statistics
|
|
282
283
|
*/
|
|
283
284
|
getStats() {
|
|
284
|
-
const
|
|
285
|
-
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
let totalRoutes = 0;
|
|
287
|
+
let maxHops = 0;
|
|
288
|
+
let hopSum = 0;
|
|
289
|
+
|
|
290
|
+
for (const [, route] of this._routes) {
|
|
291
|
+
if (now <= route.expiresAt) {
|
|
292
|
+
totalRoutes++;
|
|
293
|
+
if (route.hopCount > maxHops) {
|
|
294
|
+
maxHops = route.hopCount;
|
|
295
|
+
}
|
|
296
|
+
hopSum += route.hopCount;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
286
299
|
|
|
287
300
|
return {
|
|
288
|
-
totalRoutes
|
|
301
|
+
totalRoutes,
|
|
289
302
|
directNeighbors: this._neighbors.size,
|
|
290
|
-
maxHops:
|
|
291
|
-
avgHops:
|
|
292
|
-
? hopCounts.reduce((a, b) => a + b, 0) / hopCounts.length
|
|
293
|
-
: 0
|
|
303
|
+
maxHops: totalRoutes > 0 ? maxHops : 0,
|
|
304
|
+
avgHops: totalRoutes > 0 ? hopSum / totalRoutes : 0
|
|
294
305
|
};
|
|
295
306
|
}
|
|
296
307
|
}
|
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
const EventEmitter = require('../../utils/EventEmitter');
|
|
12
12
|
const { EVENTS } = require('../../constants');
|
|
13
13
|
const { ValidationError } = require('../../errors');
|
|
14
|
+
const { randomBytes } = require('../../utils/bytes');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hex lookup table for fast byte-to-hex conversion
|
|
18
|
+
* @constant {string[]}
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
const HEX = Array.from({ length: 256 }, (/** @type {any} */ _, /** @type {number} */ i) => (i < 16 ? '0' : '') + i.toString(16));
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* Default configuration for store and forward
|
|
17
|
-
* @constant {
|
|
25
|
+
* @constant {any}
|
|
18
26
|
*/
|
|
19
27
|
const DEFAULT_CONFIG = Object.freeze({
|
|
20
28
|
/** Maximum number of cached messages per recipient */
|
|
@@ -60,26 +68,21 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
60
68
|
class StoreAndForwardManager extends EventEmitter {
|
|
61
69
|
/**
|
|
62
70
|
* Creates a new StoreAndForwardManager instance.
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {number} [options.maxMessagesPerRecipient=100] - Max messages per recipient
|
|
65
|
-
* @param {number} [options.maxTotalMessages=1000] - Max total cached messages
|
|
66
|
-
* @param {number} [options.maxCacheSizeBytes=10485760] - Max cache size (10MB)
|
|
67
|
-
* @param {number} [options.retentionMs=86400000] - Message retention (24h)
|
|
68
|
-
* @param {number} [options.cleanupIntervalMs=300000] - Cleanup interval (5min)
|
|
71
|
+
* @param {any} [options={}] - Configuration options *
|
|
69
72
|
*/
|
|
70
73
|
constructor(options = {}) {
|
|
71
74
|
super();
|
|
72
75
|
|
|
73
76
|
/**
|
|
74
77
|
* Configuration
|
|
75
|
-
* @type {
|
|
78
|
+
* @type {any}
|
|
76
79
|
* @private
|
|
77
80
|
*/
|
|
78
81
|
this._config = { ...DEFAULT_CONFIG, ...options };
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
84
|
* Message cache by recipient ID
|
|
82
|
-
* @type {Map<string,
|
|
85
|
+
* @type {Map<string, any[]>}
|
|
83
86
|
* @private
|
|
84
87
|
*/
|
|
85
88
|
this._cache = new Map();
|
|
@@ -100,14 +103,14 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
100
103
|
|
|
101
104
|
/**
|
|
102
105
|
* Cleanup timer
|
|
103
|
-
* @type {
|
|
106
|
+
* @type {any}
|
|
104
107
|
* @private
|
|
105
108
|
*/
|
|
106
109
|
this._cleanupTimer = null;
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* Statistics
|
|
110
|
-
* @type {
|
|
113
|
+
* @type {any}
|
|
111
114
|
* @private
|
|
112
115
|
*/
|
|
113
116
|
this._stats = {
|
|
@@ -127,9 +130,9 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
127
130
|
* Caches a message for an offline peer.
|
|
128
131
|
* @param {string} recipientId - Recipient peer ID
|
|
129
132
|
* @param {Uint8Array} encryptedPayload - Encrypted message payload
|
|
130
|
-
* @param {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
* @param {any} [options={}] - Cache options
|
|
134
|
+
*
|
|
135
|
+
*
|
|
133
136
|
* @returns {Promise<string>} Cached message ID
|
|
134
137
|
*/
|
|
135
138
|
async cacheForOfflinePeer(recipientId, encryptedPayload, options = {}) {
|
|
@@ -165,7 +168,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
165
168
|
this._cache.set(recipientId, []);
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
const recipientCache = this._cache.get(recipientId);
|
|
171
|
+
const recipientCache = /** @type {any[]} */ (this._cache.get(recipientId));
|
|
169
172
|
|
|
170
173
|
// Check per-recipient limit
|
|
171
174
|
if (recipientCache.length >= this._config.maxMessagesPerRecipient) {
|
|
@@ -197,14 +200,15 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
197
200
|
* Delivers all cached messages to a peer that came online.
|
|
198
201
|
* @param {string} recipientId - Recipient peer ID
|
|
199
202
|
* @param {Function} sendFn - Async function to send message: (payload) => Promise<void>
|
|
200
|
-
* @returns {Promise<
|
|
203
|
+
* @returns {Promise<any>} Delivery result with counts
|
|
201
204
|
*/
|
|
202
205
|
async deliverCachedMessages(recipientId, sendFn) {
|
|
203
206
|
if (!this._cache.has(recipientId)) {
|
|
204
207
|
return { delivered: 0, failed: 0 };
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
const messages = this._cache.get(recipientId);
|
|
210
|
+
const messages = /** @type {any[]} */ (this._cache.get(recipientId));
|
|
211
|
+
/** @type {any} */
|
|
208
212
|
const results = { delivered: 0, failed: 0, remaining: [] };
|
|
209
213
|
|
|
210
214
|
for (const msg of messages) {
|
|
@@ -230,7 +234,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
230
234
|
messageId: msg.id,
|
|
231
235
|
recipientId
|
|
232
236
|
});
|
|
233
|
-
} catch (error) {
|
|
237
|
+
} catch (/** @type {any} */ error) {
|
|
234
238
|
results.failed++;
|
|
235
239
|
this._stats.deliveryFailures++;
|
|
236
240
|
results.remaining.push(msg);
|
|
@@ -297,7 +301,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
const count = cache.length;
|
|
300
|
-
const size = cache.reduce((sum, m) => sum + m.size, 0);
|
|
304
|
+
const size = cache.reduce((/** @type {number} */ sum, /** @type {any} */ m) => sum + m.size, 0);
|
|
301
305
|
|
|
302
306
|
this._cache.delete(recipientId);
|
|
303
307
|
this._totalSize -= size;
|
|
@@ -315,7 +319,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
315
319
|
let pruned = 0;
|
|
316
320
|
|
|
317
321
|
for (const [recipientId, messages] of this._cache) {
|
|
318
|
-
const validMessages = messages.filter(msg => {
|
|
322
|
+
const validMessages = messages.filter((/** @type {any} */ msg) => {
|
|
319
323
|
if (msg.expiresAt <= now) {
|
|
320
324
|
this._totalSize -= msg.size;
|
|
321
325
|
this._totalCount--;
|
|
@@ -342,7 +346,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
342
346
|
|
|
343
347
|
/**
|
|
344
348
|
* Gets store and forward statistics.
|
|
345
|
-
* @returns {
|
|
349
|
+
* @returns {any} Statistics
|
|
346
350
|
*/
|
|
347
351
|
getStats() {
|
|
348
352
|
return {
|
|
@@ -410,6 +414,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
410
414
|
*/
|
|
411
415
|
_removeOldestMessage() {
|
|
412
416
|
let oldestTime = Infinity;
|
|
417
|
+
/** @type {string|null} */
|
|
413
418
|
let oldestRecipient = null;
|
|
414
419
|
|
|
415
420
|
for (const [recipientId, messages] of this._cache) {
|
|
@@ -420,7 +425,7 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
420
425
|
}
|
|
421
426
|
|
|
422
427
|
if (oldestRecipient) {
|
|
423
|
-
const messages = this._cache.get(oldestRecipient);
|
|
428
|
+
const messages = /** @type {any[]} */ (this._cache.get(oldestRecipient));
|
|
424
429
|
const oldest = messages.shift();
|
|
425
430
|
if (oldest) {
|
|
426
431
|
this._totalSize -= oldest.size;
|
|
@@ -439,9 +444,12 @@ class StoreAndForwardManager extends EventEmitter {
|
|
|
439
444
|
* @private
|
|
440
445
|
*/
|
|
441
446
|
_generateId() {
|
|
442
|
-
const { randomBytes } = require('../../utils/bytes');
|
|
443
447
|
const bytes = randomBytes(16);
|
|
444
|
-
|
|
448
|
+
let id = '';
|
|
449
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
450
|
+
id += HEX[bytes[i]];
|
|
451
|
+
}
|
|
452
|
+
return id;
|
|
445
453
|
}
|
|
446
454
|
|
|
447
455
|
/**
|
|
@@ -39,15 +39,14 @@ function deserializeHeader(data) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const headerData = data.
|
|
43
|
-
const view = new DataView(
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
230
|
+
return data.subarray(8, 24);
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
module.exports = {
|