react-native-ble-mesh 2.1.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/package.json +1 -1
- package/src/MeshNetwork.js +48 -46
- 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 +2 -0
- package/src/crypto/CryptoProvider.js +17 -17
- package/src/crypto/providers/ExpoCryptoProvider.js +12 -7
- package/src/crypto/providers/QuickCryptoProvider.js +12 -7
- package/src/crypto/providers/TweetNaClProvider.js +9 -7
- 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 +2 -1
- package/src/hooks/useMesh.js +7 -7
- package/src/hooks/useMessages.js +13 -12
- package/src/hooks/usePeers.js +10 -9
- package/src/index.js +2 -2
- package/src/mesh/dedup/BloomFilter.js +1 -0
- package/src/mesh/dedup/DedupManager.js +4 -7
- package/src/mesh/dedup/MessageCache.js +3 -0
- package/src/mesh/fragment/Assembler.js +5 -4
- package/src/mesh/fragment/Fragmenter.js +2 -2
- package/src/mesh/monitor/ConnectionQuality.js +17 -8
- package/src/mesh/monitor/NetworkMonitor.js +22 -15
- package/src/mesh/peer/Peer.js +4 -9
- package/src/mesh/peer/PeerDiscovery.js +18 -19
- package/src/mesh/peer/PeerManager.js +14 -14
- package/src/mesh/router/MessageRouter.js +15 -15
- package/src/mesh/router/PathFinder.js +10 -13
- package/src/mesh/router/RouteTable.js +8 -7
- package/src/mesh/store/StoreAndForwardManager.js +20 -23
- package/src/protocol/message.js +5 -13
- package/src/protocol/serializer.js +4 -4
- package/src/protocol/validator.js +7 -6
- package/src/service/BatteryOptimizer.js +18 -17
- package/src/service/EmergencyManager.js +18 -25
- package/src/service/HandshakeManager.js +100 -2
- package/src/service/MeshService.js +106 -22
- package/src/service/SessionManager.js +38 -3
- 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 +55 -28
- 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 +22 -30
- package/src/storage/Storage.js +9 -9
- package/src/transport/BLETransport.js +16 -14
- package/src/transport/MockTransport.js +7 -2
- package/src/transport/MultiTransport.js +13 -6
- package/src/transport/Transport.js +9 -9
- package/src/transport/WiFiDirectTransport.js +22 -17
- 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 +9 -7
- package/src/utils/LRUCache.js +10 -4
- package/src/utils/RateLimiter.js +1 -1
- package/src/utils/compression.js +5 -5
- package/src/utils/encoding.js +8 -2
- package/src/utils/retry.js +11 -13
- package/src/utils/time.js +9 -4
- package/src/utils/validation.js +1 -1
|
@@ -19,21 +19,38 @@ const { TextManager, ChannelManager } = require('./text');
|
|
|
19
19
|
* @extends EventEmitter
|
|
20
20
|
*/
|
|
21
21
|
class MeshService extends EventEmitter {
|
|
22
|
+
/**
|
|
23
|
+
* @param {any} [config]
|
|
24
|
+
*/
|
|
22
25
|
constructor(config = {}) {
|
|
23
26
|
super();
|
|
27
|
+
/** @type {any} */
|
|
24
28
|
this._config = { displayName: 'Anonymous', ...config };
|
|
29
|
+
/** @type {string} */
|
|
25
30
|
this._state = SERVICE_STATE.UNINITIALIZED;
|
|
31
|
+
/** @type {any} */
|
|
26
32
|
this._transport = null;
|
|
33
|
+
/** @type {any} */
|
|
27
34
|
this._keyManager = null;
|
|
35
|
+
/** @type {SessionManager | null} */
|
|
28
36
|
this._sessionManager = null;
|
|
37
|
+
/** @type {HandshakeManager | null} */
|
|
29
38
|
this._handshakeManager = null;
|
|
39
|
+
/** @type {any} */
|
|
30
40
|
this._channelManager = null;
|
|
41
|
+
/** @type {any} */
|
|
31
42
|
this._peerManager = null;
|
|
43
|
+
/** @type {any} */
|
|
32
44
|
this._audioManager = null;
|
|
45
|
+
/** @type {any} */
|
|
33
46
|
this._textManager = null;
|
|
47
|
+
/** @type {number} */
|
|
34
48
|
this._messageCounter = 0;
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {any} [options]
|
|
53
|
+
*/
|
|
37
54
|
async initialize(options = {}) {
|
|
38
55
|
if (this._state !== SERVICE_STATE.UNINITIALIZED) {
|
|
39
56
|
throw new MeshError('Service already initialized', ERROR_CODE.E002);
|
|
@@ -47,12 +64,15 @@ class MeshService extends EventEmitter {
|
|
|
47
64
|
this._setupEventForwarding();
|
|
48
65
|
this._setState(SERVICE_STATE.READY);
|
|
49
66
|
this.emit(EVENTS.INITIALIZED);
|
|
50
|
-
} catch (err) {
|
|
67
|
+
} catch (/** @type {any} */ err) {
|
|
51
68
|
this._setState(SERVICE_STATE.ERROR);
|
|
52
69
|
throw new MeshError(`Initialization failed: ${err.message}`, ERROR_CODE.E001);
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {any} transport
|
|
75
|
+
*/
|
|
56
76
|
async start(transport) {
|
|
57
77
|
this._validateState([SERVICE_STATE.READY, SERVICE_STATE.SUSPENDED]);
|
|
58
78
|
if (!transport) { throw new ValidationError('Transport is required', ERROR_CODE.E802); }
|
|
@@ -87,31 +107,52 @@ class MeshService extends EventEmitter {
|
|
|
87
107
|
};
|
|
88
108
|
}
|
|
89
109
|
|
|
110
|
+
/**
|
|
111
|
+
* @param {string} name
|
|
112
|
+
*/
|
|
90
113
|
setDisplayName(name) { this._config.displayName = name; }
|
|
91
114
|
exportIdentity() { return this._keyManager?.exportIdentity() || null; }
|
|
115
|
+
/**
|
|
116
|
+
* @param {any} identity
|
|
117
|
+
*/
|
|
92
118
|
importIdentity(identity) { this._keyManager?.importIdentity(identity); }
|
|
93
119
|
|
|
94
120
|
getPeers() { return this._peerManager?.getAllPeers() || []; }
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} id
|
|
123
|
+
*/
|
|
95
124
|
getPeer(id) { return this._peerManager?.getPeer(id); }
|
|
96
125
|
getConnectedPeers() { return this._peerManager?.getConnectedPeers() || []; }
|
|
97
126
|
getSecuredPeers() { return this._sessionManager?.getAllSessionPeerIds() || []; }
|
|
98
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} peerId
|
|
130
|
+
*/
|
|
99
131
|
async initiateHandshake(peerId) {
|
|
100
132
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
101
|
-
return this._handshakeManager
|
|
133
|
+
return this._handshakeManager?.initiateHandshake(peerId, this._transport);
|
|
102
134
|
}
|
|
103
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} id
|
|
138
|
+
*/
|
|
104
139
|
blockPeer(id) {
|
|
105
140
|
this._peerManager?.blockPeer(id);
|
|
106
141
|
this.emit(EVENTS.PEER_BLOCKED, { peerId: id });
|
|
107
142
|
}
|
|
108
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} id
|
|
146
|
+
*/
|
|
109
147
|
unblockPeer(id) {
|
|
110
148
|
this._peerManager?.unblockPeer(id);
|
|
111
149
|
this.emit(EVENTS.PEER_UNBLOCKED, { peerId: id });
|
|
112
150
|
}
|
|
113
151
|
|
|
114
152
|
// Text messaging methods
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} content
|
|
155
|
+
*/
|
|
115
156
|
sendBroadcast(content) {
|
|
116
157
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
117
158
|
if (this._textManager) {
|
|
@@ -130,26 +171,34 @@ class MeshService extends EventEmitter {
|
|
|
130
171
|
return messageId;
|
|
131
172
|
}
|
|
132
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @param {string} peerId
|
|
176
|
+
* @param {string} content
|
|
177
|
+
*/
|
|
133
178
|
async sendPrivateMessage(peerId, content) {
|
|
134
179
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
135
180
|
if (this._textManager) {
|
|
136
181
|
return this._textManager.sendPrivateMessage(peerId, content);
|
|
137
182
|
}
|
|
138
|
-
if (!this._sessionManager
|
|
183
|
+
if (!this._sessionManager?.hasSession(peerId)) { await this.initiateHandshake(peerId); }
|
|
139
184
|
const messageId = this._generateMessageId();
|
|
140
185
|
const plaintext = new TextEncoder().encode(content);
|
|
141
|
-
const ciphertext = this._sessionManager
|
|
186
|
+
const ciphertext = this._sessionManager?.encryptFor(peerId, plaintext);
|
|
142
187
|
await this._transport.send(peerId, ciphertext);
|
|
143
188
|
this.emit(EVENTS.PRIVATE_MESSAGE_SENT, { messageId, peerId });
|
|
144
189
|
return messageId;
|
|
145
190
|
}
|
|
146
191
|
|
|
192
|
+
/**
|
|
193
|
+
* @param {string} channelId
|
|
194
|
+
* @param {string} content
|
|
195
|
+
*/
|
|
147
196
|
sendChannelMessage(channelId, content) {
|
|
148
197
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
149
198
|
if (this._textManager) {
|
|
150
199
|
return this._textManager.sendChannelMessage(channelId, content);
|
|
151
200
|
}
|
|
152
|
-
if (!this._channelManager
|
|
201
|
+
if (!this._channelManager?.isInChannel(channelId)) {
|
|
153
202
|
throw new MeshError('Not in channel', ERROR_CODE.E602);
|
|
154
203
|
}
|
|
155
204
|
const messageId = this._generateMessageId();
|
|
@@ -157,28 +206,38 @@ class MeshService extends EventEmitter {
|
|
|
157
206
|
return messageId;
|
|
158
207
|
}
|
|
159
208
|
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} channelId
|
|
211
|
+
* @param {string} [password]
|
|
212
|
+
*/
|
|
160
213
|
joinChannel(channelId, password) {
|
|
161
214
|
if (this._textManager) {
|
|
162
215
|
return this._textManager.joinChannel(channelId, password);
|
|
163
216
|
}
|
|
164
|
-
this._channelManager
|
|
217
|
+
this._channelManager?.joinChannel(channelId, password);
|
|
165
218
|
}
|
|
166
219
|
|
|
220
|
+
/**
|
|
221
|
+
* @param {string} channelId
|
|
222
|
+
*/
|
|
167
223
|
leaveChannel(channelId) {
|
|
168
224
|
if (this._textManager) {
|
|
169
225
|
return this._textManager.leaveChannel(channelId);
|
|
170
226
|
}
|
|
171
|
-
this._channelManager
|
|
227
|
+
this._channelManager?.leaveChannel(channelId);
|
|
172
228
|
}
|
|
173
229
|
|
|
174
230
|
getChannels() {
|
|
175
231
|
if (this._textManager) {
|
|
176
232
|
return this._textManager.getChannels();
|
|
177
233
|
}
|
|
178
|
-
return this._channelManager
|
|
234
|
+
return this._channelManager?.getChannels() || [];
|
|
179
235
|
}
|
|
180
236
|
|
|
181
237
|
// Text manager methods
|
|
238
|
+
/**
|
|
239
|
+
* @param {any} [options]
|
|
240
|
+
*/
|
|
182
241
|
async initializeText(options = {}) {
|
|
183
242
|
this._validateState([SERVICE_STATE.READY, SERVICE_STATE.ACTIVE]);
|
|
184
243
|
if (this._textManager) {
|
|
@@ -192,6 +251,9 @@ class MeshService extends EventEmitter {
|
|
|
192
251
|
getTextManager() { return this._textManager; }
|
|
193
252
|
|
|
194
253
|
// Audio methods
|
|
254
|
+
/**
|
|
255
|
+
* @param {any} [options]
|
|
256
|
+
*/
|
|
195
257
|
async initializeAudio(options = {}) {
|
|
196
258
|
this._validateState([SERVICE_STATE.READY, SERVICE_STATE.ACTIVE]);
|
|
197
259
|
if (this._audioManager) {
|
|
@@ -204,6 +266,10 @@ class MeshService extends EventEmitter {
|
|
|
204
266
|
|
|
205
267
|
getAudioManager() { return this._audioManager; }
|
|
206
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @param {string} peerId
|
|
271
|
+
* @param {any} voiceMessage
|
|
272
|
+
*/
|
|
207
273
|
async sendVoiceMessage(peerId, voiceMessage) {
|
|
208
274
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
209
275
|
if (!this._audioManager) {
|
|
@@ -212,6 +278,9 @@ class MeshService extends EventEmitter {
|
|
|
212
278
|
return this._audioManager.sendVoiceMessage(peerId, voiceMessage);
|
|
213
279
|
}
|
|
214
280
|
|
|
281
|
+
/**
|
|
282
|
+
* @param {string} peerId
|
|
283
|
+
*/
|
|
215
284
|
async requestAudioStream(peerId) {
|
|
216
285
|
this._validateState([SERVICE_STATE.ACTIVE]);
|
|
217
286
|
if (!this._audioManager) {
|
|
@@ -228,7 +297,7 @@ class MeshService extends EventEmitter {
|
|
|
228
297
|
EVENTS.CHANNEL_JOINED, EVENTS.CHANNEL_LEFT, EVENTS.CHANNEL_MESSAGE,
|
|
229
298
|
EVENTS.CHANNEL_MEMBER_JOINED, EVENTS.CHANNEL_MEMBER_LEFT
|
|
230
299
|
];
|
|
231
|
-
textEvents.forEach(e => this._textManager.on(e, d => this.emit(e, d)));
|
|
300
|
+
textEvents.forEach((/** @type {string} */ e) => this._textManager.on(e, (/** @type {any} */ d) => this.emit(e, d)));
|
|
232
301
|
}
|
|
233
302
|
|
|
234
303
|
_setupAudioEventForwarding() {
|
|
@@ -237,9 +306,13 @@ class MeshService extends EventEmitter {
|
|
|
237
306
|
EVENTS.AUDIO_STREAM_REQUEST, EVENTS.AUDIO_STREAM_STARTED, EVENTS.AUDIO_STREAM_ENDED,
|
|
238
307
|
EVENTS.VOICE_MESSAGE_RECEIVED, EVENTS.VOICE_MESSAGE_SENT, EVENTS.VOICE_MESSAGE_PROGRESS
|
|
239
308
|
];
|
|
240
|
-
audioEvents.forEach(e => this._audioManager.on(e, d => this.emit(e, d)));
|
|
309
|
+
audioEvents.forEach((/** @type {string} */ e) => this._audioManager.on(e, (/** @type {any} */ d) => this.emit(e, d)));
|
|
241
310
|
}
|
|
242
311
|
|
|
312
|
+
/**
|
|
313
|
+
* @param {string} peerId
|
|
314
|
+
* @param {Uint8Array} data
|
|
315
|
+
*/
|
|
243
316
|
async _sendRaw(peerId, data) {
|
|
244
317
|
if (this._state === SERVICE_STATE.DESTROYED || !this._transport) {
|
|
245
318
|
return; // Silently ignore sends after destroy
|
|
@@ -251,8 +324,8 @@ class MeshService extends EventEmitter {
|
|
|
251
324
|
return {
|
|
252
325
|
state: this._state, identity: this.getIdentity(),
|
|
253
326
|
peerCount: this.getConnectedPeers().length, securedPeerCount: this.getSecuredPeers().length,
|
|
254
|
-
channelCount: this._channelManager?.getChannels()
|
|
255
|
-
sessionCount: this._sessionManager?.getAllSessionPeerIds()
|
|
327
|
+
channelCount: this._channelManager?.getChannels()?.length || 0,
|
|
328
|
+
sessionCount: this._sessionManager?.getAllSessionPeerIds()?.length || 0,
|
|
256
329
|
hasTextManager: !!this._textManager,
|
|
257
330
|
hasAudioManager: !!this._audioManager
|
|
258
331
|
};
|
|
@@ -260,12 +333,18 @@ class MeshService extends EventEmitter {
|
|
|
260
333
|
|
|
261
334
|
getState() { return this._state; }
|
|
262
335
|
|
|
336
|
+
/**
|
|
337
|
+
* @param {string} newState
|
|
338
|
+
*/
|
|
263
339
|
_setState(newState) {
|
|
264
340
|
const oldState = this._state;
|
|
265
341
|
this._state = newState;
|
|
266
342
|
this.emit(EVENTS.STATE_CHANGED, { oldState, newState });
|
|
267
343
|
}
|
|
268
344
|
|
|
345
|
+
/**
|
|
346
|
+
* @param {string[]} allowed
|
|
347
|
+
*/
|
|
269
348
|
_validateState(allowed) {
|
|
270
349
|
if (!allowed.includes(this._state)) {
|
|
271
350
|
throw new MeshError(`Invalid state: ${this._state}`, ERROR_CODE.E003);
|
|
@@ -276,13 +355,15 @@ class MeshService extends EventEmitter {
|
|
|
276
355
|
|
|
277
356
|
_createKeyManager() {
|
|
278
357
|
const { createProvider } = require('../crypto/AutoCrypto');
|
|
358
|
+
/** @type {any} */
|
|
279
359
|
let provider;
|
|
360
|
+
/** @type {any} */
|
|
280
361
|
let keyPair;
|
|
281
362
|
|
|
282
363
|
try {
|
|
283
364
|
provider = createProvider('auto');
|
|
284
365
|
keyPair = provider.generateKeyPair();
|
|
285
|
-
} catch (e) {
|
|
366
|
+
} catch (/** @type {any} */ e) {
|
|
286
367
|
// If no crypto provider is available, return a minimal fallback
|
|
287
368
|
// that generates random keys using basic randomBytes
|
|
288
369
|
const { randomBytes } = require('../utils/bytes');
|
|
@@ -291,7 +372,7 @@ class MeshService extends EventEmitter {
|
|
|
291
372
|
getStaticKeyPair: () => keyPair,
|
|
292
373
|
getPublicKey: () => keyPair.publicKey,
|
|
293
374
|
exportIdentity: () => ({ publicKey: Array.from(keyPair.publicKey) }),
|
|
294
|
-
importIdentity: (id) => {
|
|
375
|
+
importIdentity: (/** @type {any} */ id) => {
|
|
295
376
|
if (id && id.publicKey) {
|
|
296
377
|
keyPair.publicKey = new Uint8Array(id.publicKey);
|
|
297
378
|
}
|
|
@@ -310,7 +391,7 @@ class MeshService extends EventEmitter {
|
|
|
310
391
|
publicKey: Array.from(keyPair.publicKey),
|
|
311
392
|
secretKey: Array.from(keyPair.secretKey)
|
|
312
393
|
}),
|
|
313
|
-
importIdentity: (id) => {
|
|
394
|
+
importIdentity: (/** @type {any} */ id) => {
|
|
314
395
|
if (id && id.publicKey) {
|
|
315
396
|
keyPair.publicKey = new Uint8Array(id.publicKey);
|
|
316
397
|
}
|
|
@@ -322,7 +403,7 @@ class MeshService extends EventEmitter {
|
|
|
322
403
|
}
|
|
323
404
|
|
|
324
405
|
_setupEventForwarding() {
|
|
325
|
-
const fwd = (em, evts) => evts.forEach(e => em.on(e, d => this.emit(e, d)));
|
|
406
|
+
const fwd = (/** @type {any} */ em, /** @type {string[]} */ evts) => evts.forEach((/** @type {string} */ e) => em.on(e, (/** @type {any} */ d) => this.emit(e, d)));
|
|
326
407
|
fwd(this._handshakeManager, [EVENTS.HANDSHAKE_STARTED, EVENTS.HANDSHAKE_PROGRESS,
|
|
327
408
|
EVENTS.HANDSHAKE_COMPLETE, EVENTS.HANDSHAKE_FAILED]);
|
|
328
409
|
fwd(this._channelManager, [EVENTS.CHANNEL_JOINED, EVENTS.CHANNEL_LEFT,
|
|
@@ -330,15 +411,18 @@ class MeshService extends EventEmitter {
|
|
|
330
411
|
}
|
|
331
412
|
|
|
332
413
|
_setupTransportListeners() {
|
|
333
|
-
this._transport.on('message', d => this._handleIncoming(d));
|
|
334
|
-
this._transport.on('peerConnected', d => this.emit(EVENTS.PEER_CONNECTED, d));
|
|
335
|
-
this._transport.on('peerDisconnected', d => this.emit(EVENTS.PEER_DISCONNECTED, d));
|
|
414
|
+
this._transport.on('message', (/** @type {any} */ d) => this._handleIncoming(d));
|
|
415
|
+
this._transport.on('peerConnected', (/** @type {any} */ d) => this.emit(EVENTS.PEER_CONNECTED, d));
|
|
416
|
+
this._transport.on('peerDisconnected', (/** @type {any} */ d) => this.emit(EVENTS.PEER_DISCONNECTED, d));
|
|
336
417
|
}
|
|
337
418
|
|
|
419
|
+
/**
|
|
420
|
+
* @param {any} param0
|
|
421
|
+
*/
|
|
338
422
|
_handleIncoming({ peerId, data }) {
|
|
339
423
|
const type = data[0], payload = data.subarray(1);
|
|
340
424
|
if (type >= MESSAGE_TYPE.HANDSHAKE_INIT && type <= MESSAGE_TYPE.HANDSHAKE_FINAL) {
|
|
341
|
-
this._handshakeManager
|
|
425
|
+
this._handshakeManager?.handleIncomingHandshake(peerId, type, payload, this._transport);
|
|
342
426
|
} else if (type === MESSAGE_TYPE.CHANNEL_MESSAGE) {
|
|
343
427
|
if (this._textManager) {
|
|
344
428
|
this._textManager.handleIncomingMessage(peerId, type, payload);
|
|
@@ -351,7 +435,7 @@ class MeshService extends EventEmitter {
|
|
|
351
435
|
const parsed = JSON.parse(decoded);
|
|
352
436
|
channelId = parsed.channelId || '';
|
|
353
437
|
content = parsed.content ? new TextEncoder().encode(parsed.content) : payload;
|
|
354
|
-
} catch (e) {
|
|
438
|
+
} catch (/** @type {any} */ e) {
|
|
355
439
|
// If not JSON, try to extract channelId as length-prefixed string
|
|
356
440
|
if (payload.length > 1) {
|
|
357
441
|
const channelIdLen = payload[0];
|
|
@@ -361,7 +445,7 @@ class MeshService extends EventEmitter {
|
|
|
361
445
|
}
|
|
362
446
|
}
|
|
363
447
|
}
|
|
364
|
-
this._channelManager
|
|
448
|
+
this._channelManager?.handleChannelMessage({ channelId, senderId: peerId, content });
|
|
365
449
|
}
|
|
366
450
|
} else if (type >= MESSAGE_TYPE.VOICE_MESSAGE_START && type <= MESSAGE_TYPE.AUDIO_STREAM_END) {
|
|
367
451
|
if (this._audioManager) {
|
|
@@ -16,9 +16,14 @@ const MAX_MESSAGE_COUNT = 1000000; // 1 million messages before nonce exhaustion
|
|
|
16
16
|
*/
|
|
17
17
|
class SessionManager {
|
|
18
18
|
constructor() {
|
|
19
|
+
/** @type {Map<string, any>} */
|
|
19
20
|
this._sessions = new Map();
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} peerId
|
|
25
|
+
* @param {any} session
|
|
26
|
+
*/
|
|
22
27
|
createSession(peerId, session) {
|
|
23
28
|
if (!peerId || typeof peerId !== 'string') {
|
|
24
29
|
throw new Error('Invalid peerId: must be a non-empty string');
|
|
@@ -31,14 +36,29 @@ class SessionManager {
|
|
|
31
36
|
});
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} peerId
|
|
41
|
+
* @returns {any}
|
|
42
|
+
*/
|
|
34
43
|
getSession(peerId) {
|
|
35
44
|
const entry = this._sessions.get(peerId);
|
|
36
45
|
return entry ? entry.session : undefined;
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} peerId
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
39
52
|
hasSession(peerId) { return this._sessions.has(peerId); }
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} peerId
|
|
55
|
+
*/
|
|
40
56
|
removeSession(peerId) { this._sessions.delete(peerId); }
|
|
41
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} peerId
|
|
60
|
+
* @param {Uint8Array} plaintext
|
|
61
|
+
*/
|
|
42
62
|
encryptFor(peerId, plaintext) {
|
|
43
63
|
const entry = this._sessions.get(peerId);
|
|
44
64
|
if (!entry) { throw CryptoError.encryptionFailed({ reason: 'Session not found', peerId }); }
|
|
@@ -60,11 +80,15 @@ class SessionManager {
|
|
|
60
80
|
entry.lastUsedAt = Date.now();
|
|
61
81
|
entry.messageCount++;
|
|
62
82
|
return ciphertext;
|
|
63
|
-
} catch (error) {
|
|
83
|
+
} catch (/** @type {any} */ error) {
|
|
64
84
|
throw CryptoError.encryptionFailed({ reason: error.message, peerId });
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} peerId
|
|
90
|
+
* @param {Uint8Array} ciphertext
|
|
91
|
+
*/
|
|
68
92
|
decryptFrom(peerId, ciphertext) {
|
|
69
93
|
const entry = this._sessions.get(peerId);
|
|
70
94
|
if (!entry) { throw CryptoError.decryptionFailed({ reason: 'Session not found', peerId }); }
|
|
@@ -75,11 +99,14 @@ class SessionManager {
|
|
|
75
99
|
entry.messageCount++;
|
|
76
100
|
}
|
|
77
101
|
return plaintext;
|
|
78
|
-
} catch (error) {
|
|
102
|
+
} catch (/** @type {any} */ error) {
|
|
79
103
|
throw CryptoError.decryptionFailed({ reason: error.message, peerId });
|
|
80
104
|
}
|
|
81
105
|
}
|
|
82
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @param {string} peerId
|
|
109
|
+
*/
|
|
83
110
|
exportSession(peerId) {
|
|
84
111
|
const entry = this._sessions.get(peerId);
|
|
85
112
|
if (!entry || typeof entry.session.export !== 'function') { return null; }
|
|
@@ -89,6 +116,10 @@ class SessionManager {
|
|
|
89
116
|
};
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} peerId
|
|
121
|
+
* @param {any} state
|
|
122
|
+
*/
|
|
92
123
|
importSession(peerId, state) {
|
|
93
124
|
if (!state || !state.sessionData) { throw new Error('Invalid session state'); }
|
|
94
125
|
|
|
@@ -112,11 +143,12 @@ class SessionManager {
|
|
|
112
143
|
let recvNonce = data.recvNonce || 0;
|
|
113
144
|
|
|
114
145
|
// Try to get crypto provider for real encrypt/decrypt
|
|
146
|
+
/** @type {any} */
|
|
115
147
|
let provider = null;
|
|
116
148
|
try {
|
|
117
149
|
const { createProvider } = require('../crypto/AutoCrypto');
|
|
118
150
|
provider = createProvider('auto');
|
|
119
|
-
} catch (e) {
|
|
151
|
+
} catch (/** @type {any} */ e) {
|
|
120
152
|
// No crypto provider available
|
|
121
153
|
}
|
|
122
154
|
|
|
@@ -127,6 +159,7 @@ class SessionManager {
|
|
|
127
159
|
const recvNonceView = new DataView(recvNonceBuf.buffer);
|
|
128
160
|
|
|
129
161
|
const session = {
|
|
162
|
+
/** @param {Uint8Array} plaintext */
|
|
130
163
|
encrypt: (plaintext) => {
|
|
131
164
|
if (provider && typeof provider.encrypt === 'function') {
|
|
132
165
|
sendNonceView.setUint32(16, 0, true);
|
|
@@ -135,6 +168,7 @@ class SessionManager {
|
|
|
135
168
|
}
|
|
136
169
|
return plaintext;
|
|
137
170
|
},
|
|
171
|
+
/** @param {Uint8Array} ciphertext */
|
|
138
172
|
decrypt: (ciphertext) => {
|
|
139
173
|
if (provider && typeof provider.decrypt === 'function') {
|
|
140
174
|
recvNonceView.setUint32(16, 0, true);
|
|
@@ -160,6 +194,7 @@ class SessionManager {
|
|
|
160
194
|
});
|
|
161
195
|
}
|
|
162
196
|
|
|
197
|
+
/** @returns {string[]} */
|
|
163
198
|
getAllSessionPeerIds() { return Array.from(this._sessions.keys()); }
|
|
164
199
|
clear() { this._sessions.clear(); }
|
|
165
200
|
|