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
|
@@ -20,11 +20,7 @@ const LC3Decoder = require('../codec/LC3Decoder');
|
|
|
20
20
|
class AudioSession extends EventEmitter {
|
|
21
21
|
/**
|
|
22
22
|
* Creates a new AudioSession
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {string} options.peerId - Remote peer ID
|
|
25
|
-
* @param {LC3Codec} options.codec - Initialized codec
|
|
26
|
-
* @param {boolean} options.isInitiator - Whether local peer initiated
|
|
27
|
-
* @param {Function} [options.sendCallback] - Callback to send data
|
|
23
|
+
* @param {any} options - Session options
|
|
28
24
|
*/
|
|
29
25
|
constructor(options) {
|
|
30
26
|
super();
|
|
@@ -35,15 +31,15 @@ class AudioSession extends EventEmitter {
|
|
|
35
31
|
throw AudioError.codecInitFailed({ reason: 'Codec must be initialized' });
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
/** @private */
|
|
34
|
+
/** @type {string} @private */
|
|
39
35
|
this._peerId = peerId;
|
|
40
|
-
/** @private */
|
|
36
|
+
/** @type {any} @private */
|
|
41
37
|
this._codec = codec;
|
|
42
|
-
/** @private */
|
|
38
|
+
/** @type {boolean} @private */
|
|
43
39
|
this._isInitiator = isInitiator;
|
|
44
|
-
/** @private */
|
|
40
|
+
/** @type {Function} @private */
|
|
45
41
|
this._sendCallback = sendCallback || (() => {});
|
|
46
|
-
/** @private */
|
|
42
|
+
/** @type {string} @private */
|
|
47
43
|
this._state = AUDIO_SESSION_STATE.IDLE;
|
|
48
44
|
/** @private */
|
|
49
45
|
this._encoder = new LC3Encoder(codec);
|
|
@@ -54,11 +50,11 @@ class AudioSession extends EventEmitter {
|
|
|
54
50
|
depth: AUDIO_STREAM_CONFIG.JITTER_BUFFER_FRAMES,
|
|
55
51
|
frameMs: codec.getConfig().frameMs
|
|
56
52
|
});
|
|
57
|
-
/** @private */
|
|
53
|
+
/** @type {number} @private */
|
|
58
54
|
this._sendSequence = 0;
|
|
59
|
-
/** @private */
|
|
55
|
+
/** @type {number | null} @private */
|
|
60
56
|
this._startTime = null;
|
|
61
|
-
/** @private */
|
|
57
|
+
/** @type {any} @private */
|
|
62
58
|
this._stats = {
|
|
63
59
|
framesSent: 0,
|
|
64
60
|
framesReceived: 0,
|
|
@@ -74,7 +70,7 @@ class AudioSession extends EventEmitter {
|
|
|
74
70
|
* @private
|
|
75
71
|
*/
|
|
76
72
|
_setupBufferEvents() {
|
|
77
|
-
this._jitterBuffer.on('underrun', (data) => {
|
|
73
|
+
this._jitterBuffer.on('underrun', (/** @type {any} */ data) => {
|
|
78
74
|
this.emit('underrun', data);
|
|
79
75
|
});
|
|
80
76
|
|
|
@@ -110,7 +106,7 @@ class AudioSession extends EventEmitter {
|
|
|
110
106
|
const frames = await this._encoder.pushSamples(samples);
|
|
111
107
|
|
|
112
108
|
for (const frame of frames) {
|
|
113
|
-
const timestampDelta = Date.now() - this._startTime;
|
|
109
|
+
const timestampDelta = Date.now() - (this._startTime || 0);
|
|
114
110
|
|
|
115
111
|
await this._sendCallback({
|
|
116
112
|
peerId: this._peerId,
|
|
@@ -142,7 +138,7 @@ class AudioSession extends EventEmitter {
|
|
|
142
138
|
|
|
143
139
|
/**
|
|
144
140
|
* Gets decoded audio for playback
|
|
145
|
-
* @returns {Promise<
|
|
141
|
+
* @returns {Promise<any>}
|
|
146
142
|
*/
|
|
147
143
|
async getAudio() {
|
|
148
144
|
if (this._state !== AUDIO_SESSION_STATE.ACTIVE) {
|
|
@@ -215,6 +211,7 @@ class AudioSession extends EventEmitter {
|
|
|
215
211
|
|
|
216
212
|
/**
|
|
217
213
|
* Sets session state
|
|
214
|
+
* @param {string} newState
|
|
218
215
|
* @private
|
|
219
216
|
*/
|
|
220
217
|
_setState(newState) {
|
|
@@ -249,7 +246,7 @@ class AudioSession extends EventEmitter {
|
|
|
249
246
|
|
|
250
247
|
/**
|
|
251
248
|
* Returns session statistics
|
|
252
|
-
* @returns {
|
|
249
|
+
* @returns {any}
|
|
253
250
|
*/
|
|
254
251
|
getStats() {
|
|
255
252
|
const bufferStats = this._jitterBuffer.getStats();
|
|
@@ -29,17 +29,14 @@ const VOICE_MESSAGE_MAGIC = new Uint8Array([0x56, 0x4D, 0x53, 0x47]); // 'VMSG'
|
|
|
29
29
|
class VoiceMessage {
|
|
30
30
|
/**
|
|
31
31
|
* Creates a VoiceMessage from recorded frames
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {FrameBuffer} options.frames - Recorded frames
|
|
34
|
-
* @param {Object} options.metadata - Recording metadata
|
|
35
|
-
* @param {Uint8Array} options.senderId - Sender ID (32 bytes)
|
|
32
|
+
* @param {any} options - Message options
|
|
36
33
|
*/
|
|
37
34
|
constructor(options) {
|
|
38
35
|
const { frames, metadata, senderId } = options;
|
|
39
36
|
|
|
40
|
-
/** @private */
|
|
37
|
+
/** @type {any} @private */
|
|
41
38
|
this._frames = frames;
|
|
42
|
-
/** @private */
|
|
39
|
+
/** @type {any} @private */
|
|
43
40
|
this._metadata = {
|
|
44
41
|
version: 1,
|
|
45
42
|
codec: metadata.codec || AUDIO_CODEC_TYPE.LC3,
|
|
@@ -49,17 +46,15 @@ class VoiceMessage {
|
|
|
49
46
|
channels: metadata.channels || 1,
|
|
50
47
|
createdAt: metadata.createdAt || Date.now()
|
|
51
48
|
};
|
|
52
|
-
/** @private */
|
|
49
|
+
/** @type {Uint8Array} @private */
|
|
53
50
|
this._senderId = senderId || new Uint8Array(32);
|
|
54
|
-
/** @private */
|
|
51
|
+
/** @type {number} @private */
|
|
55
52
|
this._playbackPosition = 0;
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
/**
|
|
59
56
|
* Starts recording a new voice message
|
|
60
|
-
* @param {
|
|
61
|
-
* @param {LC3Codec} options.codec - Initialized codec
|
|
62
|
-
* @param {Uint8Array} options.senderId - Sender ID
|
|
57
|
+
* @param {any} options - Recording options
|
|
63
58
|
* @returns {VoiceMessageRecorder}
|
|
64
59
|
*/
|
|
65
60
|
static startRecording(options) {
|
|
@@ -160,7 +155,7 @@ class VoiceMessage {
|
|
|
160
155
|
|
|
161
156
|
/**
|
|
162
157
|
* Returns metadata
|
|
163
|
-
* @returns {
|
|
158
|
+
* @returns {any}
|
|
164
159
|
*/
|
|
165
160
|
getMetadata() {
|
|
166
161
|
return {
|
|
@@ -190,7 +185,7 @@ class VoiceMessage {
|
|
|
190
185
|
|
|
191
186
|
/**
|
|
192
187
|
* Gets next frame for playback
|
|
193
|
-
* @returns {
|
|
188
|
+
* @returns {any}
|
|
194
189
|
*/
|
|
195
190
|
getNextFrame() {
|
|
196
191
|
if (this._playbackPosition >= this._frames.getFrameCount()) {
|
|
@@ -237,9 +232,7 @@ class VoiceMessage {
|
|
|
237
232
|
class VoiceMessageRecorder extends EventEmitter {
|
|
238
233
|
/**
|
|
239
234
|
* Creates a new recorder
|
|
240
|
-
* @param {
|
|
241
|
-
* @param {LC3Codec} options.codec - Initialized codec
|
|
242
|
-
* @param {Uint8Array} options.senderId - Sender ID
|
|
235
|
+
* @param {any} options - Recorder options
|
|
243
236
|
*/
|
|
244
237
|
constructor(options) {
|
|
245
238
|
super();
|
|
@@ -250,19 +243,19 @@ class VoiceMessageRecorder extends EventEmitter {
|
|
|
250
243
|
throw AudioError.codecInitFailed({ reason: 'Codec must be initialized' });
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
/** @private */
|
|
246
|
+
/** @type {any} @private */
|
|
254
247
|
this._codec = codec;
|
|
255
|
-
/** @private */
|
|
248
|
+
/** @type {Uint8Array} @private */
|
|
256
249
|
this._senderId = senderId || new Uint8Array(32);
|
|
257
250
|
/** @private */
|
|
258
251
|
this._frames = new FrameBuffer();
|
|
259
|
-
/** @private */
|
|
252
|
+
/** @type {any} @private */
|
|
260
253
|
this._encoder = null;
|
|
261
|
-
/** @private */
|
|
254
|
+
/** @type {number} @private */
|
|
262
255
|
this._startTime = Date.now();
|
|
263
|
-
/** @private */
|
|
256
|
+
/** @type {boolean} @private */
|
|
264
257
|
this._cancelled = false;
|
|
265
|
-
/** @private */
|
|
258
|
+
/** @type {any} @private */
|
|
266
259
|
this._config = codec.getConfig();
|
|
267
260
|
|
|
268
261
|
// Lazy load encoder
|
|
@@ -17,15 +17,15 @@ class AudioFragmenter {
|
|
|
17
17
|
/**
|
|
18
18
|
* Fragments voice message data
|
|
19
19
|
* @param {Uint8Array} voiceData - Serialized voice message
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {string} metadata.messageId - Unique message ID
|
|
20
|
+
* @param {any} metadata - Voice message metadata
|
|
22
21
|
* @param {number} [chunkSize] - Chunk size (default from config)
|
|
23
22
|
* @returns {Uint8Array[]} Array of fragments
|
|
24
23
|
*/
|
|
25
24
|
static fragment(voiceData, metadata, chunkSize = VOICE_MESSAGE_CONFIG.CHUNK_SIZE) {
|
|
26
25
|
const { messageId } = metadata;
|
|
27
|
-
const messageIdBytes =
|
|
26
|
+
const messageIdBytes = AudioFragmenter._stringToBytes(messageId, 16);
|
|
28
27
|
const totalChunks = Math.ceil(voiceData.length / chunkSize);
|
|
28
|
+
/** @type {Uint8Array[]} */
|
|
29
29
|
const fragments = [];
|
|
30
30
|
|
|
31
31
|
for (let i = 0; i < totalChunks; i++) {
|
|
@@ -77,12 +77,15 @@ class AudioFragmenter {
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Converts string to fixed-length bytes
|
|
80
|
+
* @param {string} str - String to convert
|
|
81
|
+
* @param {number} length - Target byte length
|
|
82
|
+
* @returns {Uint8Array}
|
|
80
83
|
* @private
|
|
81
84
|
*/
|
|
82
85
|
static _stringToBytes(str, length) {
|
|
83
86
|
const bytes = new Uint8Array(length);
|
|
84
87
|
const encoder = new TextEncoder();
|
|
85
|
-
const encoded = encoder.encode(str.slice(0, length));
|
|
88
|
+
const encoded = encoder.encode(String(str).slice(0, length));
|
|
86
89
|
bytes.set(encoded.slice(0, length));
|
|
87
90
|
return bytes;
|
|
88
91
|
}
|
|
@@ -96,15 +99,14 @@ class AudioFragmenter {
|
|
|
96
99
|
class AudioAssembler extends EventEmitter {
|
|
97
100
|
/**
|
|
98
101
|
* Creates a new AudioAssembler
|
|
99
|
-
* @param {
|
|
100
|
-
* @param {number} [options.timeout=120000] - Assembly timeout in ms
|
|
102
|
+
* @param {any} [options] - Assembler options
|
|
101
103
|
*/
|
|
102
104
|
constructor(options = {}) {
|
|
103
105
|
super();
|
|
104
106
|
|
|
105
|
-
/** @private */
|
|
107
|
+
/** @type {number} @private */
|
|
106
108
|
this._timeout = options.timeout || VOICE_MESSAGE_CONFIG.TIMEOUT_MS;
|
|
107
|
-
/** @private */
|
|
109
|
+
/** @type {Map<string, any>} @private */
|
|
108
110
|
this._pending = new Map(); // messageId -> { fragments, totalSize, receivedSize, timer }
|
|
109
111
|
}
|
|
110
112
|
|
|
@@ -120,6 +122,7 @@ class AudioAssembler extends EventEmitter {
|
|
|
120
122
|
const index = view.getUint16(17, false);
|
|
121
123
|
const total = view.getUint16(19, false);
|
|
122
124
|
|
|
125
|
+
/** @type {Uint8Array} */
|
|
123
126
|
let chunkData;
|
|
124
127
|
let totalSize = 0;
|
|
125
128
|
|
|
@@ -171,6 +174,8 @@ class AudioAssembler extends EventEmitter {
|
|
|
171
174
|
|
|
172
175
|
/**
|
|
173
176
|
* Assembles complete voice message
|
|
177
|
+
* @param {string} messageId - Message ID
|
|
178
|
+
* @returns {Uint8Array|null}
|
|
174
179
|
* @private
|
|
175
180
|
*/
|
|
176
181
|
_assemble(messageId) {
|
|
@@ -199,6 +204,7 @@ class AudioAssembler extends EventEmitter {
|
|
|
199
204
|
|
|
200
205
|
/**
|
|
201
206
|
* Handles assembly timeout
|
|
207
|
+
* @param {string} messageId - Message ID
|
|
202
208
|
* @private
|
|
203
209
|
*/
|
|
204
210
|
_handleTimeout(messageId) {
|
|
@@ -209,7 +215,7 @@ class AudioAssembler extends EventEmitter {
|
|
|
209
215
|
/**
|
|
210
216
|
* Gets assembly progress
|
|
211
217
|
* @param {string} messageId - Message ID
|
|
212
|
-
* @returns {
|
|
218
|
+
* @returns {any}
|
|
213
219
|
*/
|
|
214
220
|
getProgress(messageId) {
|
|
215
221
|
const entry = this._pending.get(messageId);
|
|
@@ -245,6 +251,8 @@ class AudioAssembler extends EventEmitter {
|
|
|
245
251
|
|
|
246
252
|
/**
|
|
247
253
|
* Converts bytes to string
|
|
254
|
+
* @param {Uint8Array} bytes - Bytes to convert
|
|
255
|
+
* @returns {string}
|
|
248
256
|
* @private
|
|
249
257
|
*/
|
|
250
258
|
_bytesToString(bytes) {
|
|
@@ -16,7 +16,7 @@ const AUDIO_FRAME_HEADER_SIZE = AUDIO_STREAM_CONFIG.FRAME_HEADER_SIZE;
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Audio frame flags
|
|
19
|
-
* @constant {
|
|
19
|
+
* @constant {any}
|
|
20
20
|
*/
|
|
21
21
|
const FRAME_FLAGS = Object.freeze({
|
|
22
22
|
NONE: 0x00,
|
|
@@ -27,12 +27,7 @@ const FRAME_FLAGS = Object.freeze({
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Packs an audio frame with header for transmission
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {number} options.type - Message type
|
|
32
|
-
* @param {Uint8Array} options.frame - Audio frame data
|
|
33
|
-
* @param {number} options.sequenceNumber - Sequence number
|
|
34
|
-
* @param {number} [options.timestampDelta=0] - Timestamp delta
|
|
35
|
-
* @param {number} [options.flags=0] - Frame flags
|
|
30
|
+
* @param {any} options - Frame options
|
|
36
31
|
* @returns {Uint8Array} Packed frame with header
|
|
37
32
|
*/
|
|
38
33
|
function packFrame(options) {
|
|
@@ -64,7 +59,7 @@ function packFrame(options) {
|
|
|
64
59
|
/**
|
|
65
60
|
* Unpacks an audio frame from received data
|
|
66
61
|
* @param {Uint8Array} data - Received data
|
|
67
|
-
* @returns {
|
|
62
|
+
* @returns {any} Unpacked frame info
|
|
68
63
|
*/
|
|
69
64
|
function unpackFrame(data) {
|
|
70
65
|
if (data.length < AUDIO_FRAME_HEADER_SIZE) {
|
|
@@ -99,13 +94,13 @@ function unpackFrame(data) {
|
|
|
99
94
|
|
|
100
95
|
/**
|
|
101
96
|
* Packs multiple frames into a single packet
|
|
102
|
-
* @param {
|
|
97
|
+
* @param {any[]} frames - Array of frame objects
|
|
103
98
|
* @returns {Uint8Array} Packed multi-frame data
|
|
104
99
|
*/
|
|
105
100
|
function packMultiFrame(frames) {
|
|
106
101
|
// Format: [count(1)][frame1][frame2]...
|
|
107
|
-
const packedFrames = frames.map(f => packFrame(f));
|
|
108
|
-
const totalLen = 1 + packedFrames.reduce((sum, p) => sum + p.length, 0);
|
|
102
|
+
const packedFrames = frames.map((/** @type {any} */ f) => packFrame(f));
|
|
103
|
+
const totalLen = 1 + packedFrames.reduce((/** @type {number} */ sum, /** @type {any} */ p) => sum + p.length, 0);
|
|
109
104
|
|
|
110
105
|
const result = new Uint8Array(totalLen);
|
|
111
106
|
result[0] = frames.length;
|
|
@@ -122,7 +117,7 @@ function packMultiFrame(frames) {
|
|
|
122
117
|
/**
|
|
123
118
|
* Unpacks multiple frames from a packet
|
|
124
119
|
* @param {Uint8Array} data - Packed multi-frame data
|
|
125
|
-
* @returns {
|
|
120
|
+
* @returns {any[]} Array of unpacked frames
|
|
126
121
|
*/
|
|
127
122
|
function unpackMultiFrame(data) {
|
|
128
123
|
if (data.length < 1) {
|
|
@@ -130,6 +125,7 @@ function unpackMultiFrame(data) {
|
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
const count = data[0];
|
|
128
|
+
/** @type {any[]} */
|
|
133
129
|
const frames = [];
|
|
134
130
|
let offset = 1;
|
|
135
131
|
|
|
@@ -84,8 +84,10 @@ class FileAssembler {
|
|
|
84
84
|
|
|
85
85
|
for (let i = 0; i < this._totalChunks; i++) {
|
|
86
86
|
const chunk = this._chunks.get(i);
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (chunk) {
|
|
88
|
+
result.set(chunk, offset);
|
|
89
|
+
offset += chunk.length;
|
|
90
|
+
}
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
// Free chunk memory immediately after assembly
|
|
@@ -27,7 +27,7 @@ class FileChunker {
|
|
|
27
27
|
* Splits data into chunks
|
|
28
28
|
* @param {Uint8Array} data - File data
|
|
29
29
|
* @param {string} transferId - Transfer ID
|
|
30
|
-
* @returns {
|
|
30
|
+
* @returns {any[]} Array of chunk objects
|
|
31
31
|
* @throws {Error} If data exceeds max file size
|
|
32
32
|
*/
|
|
33
33
|
chunk(data, transferId) {
|
|
@@ -12,7 +12,7 @@ const { FileMessage, FILE_TRANSFER_STATE } = require('./FileMessage');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Default file transfer configuration
|
|
15
|
-
* @constant {
|
|
15
|
+
* @constant {any}
|
|
16
16
|
*/
|
|
17
17
|
const DEFAULT_CONFIG = Object.freeze({
|
|
18
18
|
chunkSize: 4096,
|
|
@@ -36,21 +36,22 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
36
36
|
*/
|
|
37
37
|
class FileManager extends EventEmitter {
|
|
38
38
|
/**
|
|
39
|
-
* @param {
|
|
39
|
+
* @param {any} [config]
|
|
40
40
|
*/
|
|
41
41
|
constructor(config = {}) {
|
|
42
42
|
super();
|
|
43
|
+
/** @type {any} */
|
|
43
44
|
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
44
45
|
this._chunker = new FileChunker({
|
|
45
46
|
chunkSize: this._config.chunkSize,
|
|
46
47
|
maxFileSize: this._config.maxFileSize
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
/** @type {Map<string,
|
|
50
|
+
/** @type {Map<string, any>} Active outgoing transfers */
|
|
50
51
|
this._outgoing = new Map();
|
|
51
|
-
/** @type {Map<string,
|
|
52
|
+
/** @type {Map<string, any>} Active incoming transfers */
|
|
52
53
|
this._incoming = new Map();
|
|
53
|
-
/** @type {Map<string,
|
|
54
|
+
/** @type {Map<string, any>} Transfer timeouts */
|
|
54
55
|
this._timeouts = new Map();
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -59,11 +60,8 @@ class FileManager extends EventEmitter {
|
|
|
59
60
|
* The caller (MeshNetwork) is responsible for actually sending chunks via transport.
|
|
60
61
|
*
|
|
61
62
|
* @param {string} peerId - Target peer ID
|
|
62
|
-
* @param {
|
|
63
|
-
* @
|
|
64
|
-
* @param {string} fileInfo.name - File name
|
|
65
|
-
* @param {string} [fileInfo.mimeType='application/octet-stream'] - MIME type
|
|
66
|
-
* @returns {Object} Transfer object with id, offer, and chunks
|
|
63
|
+
* @param {any} fileInfo - File information
|
|
64
|
+
* @returns {any} Transfer object with id, offer, and chunks
|
|
67
65
|
*/
|
|
68
66
|
prepareSend(peerId, fileInfo) {
|
|
69
67
|
if (this._outgoing.size >= this._config.maxConcurrentTransfers) {
|
|
@@ -105,7 +103,7 @@ class FileManager extends EventEmitter {
|
|
|
105
103
|
/**
|
|
106
104
|
* Marks a chunk as sent and emits progress
|
|
107
105
|
* @param {string} transferId - Transfer ID
|
|
108
|
-
* @param {number}
|
|
106
|
+
* @param {number} _chunkIndex - Chunk index that was sent
|
|
109
107
|
*/
|
|
110
108
|
markChunkSent(transferId, _chunkIndex) {
|
|
111
109
|
const transfer = this._outgoing.get(transferId);
|
|
@@ -141,7 +139,7 @@ class FileManager extends EventEmitter {
|
|
|
141
139
|
|
|
142
140
|
/**
|
|
143
141
|
* Handles an incoming file offer
|
|
144
|
-
* @param {
|
|
142
|
+
* @param {any} offer - File offer metadata
|
|
145
143
|
* @param {string} senderId - Sender peer ID
|
|
146
144
|
* @returns {string} Transfer ID
|
|
147
145
|
*/
|
|
@@ -234,15 +232,17 @@ class FileManager extends EventEmitter {
|
|
|
234
232
|
|
|
235
233
|
if (this._outgoing.has(transferId)) {
|
|
236
234
|
const transfer = this._outgoing.get(transferId);
|
|
237
|
-
transfer.state = FILE_TRANSFER_STATE.CANCELLED;
|
|
235
|
+
if (transfer) { transfer.state = FILE_TRANSFER_STATE.CANCELLED; }
|
|
238
236
|
this._outgoing.delete(transferId);
|
|
239
237
|
this.emit('transferCancelled', { transferId, direction: 'outgoing' });
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
if (this._incoming.has(transferId)) {
|
|
243
241
|
const transfer = this._incoming.get(transferId);
|
|
244
|
-
transfer
|
|
245
|
-
|
|
242
|
+
if (transfer) {
|
|
243
|
+
transfer.meta.state = FILE_TRANSFER_STATE.CANCELLED;
|
|
244
|
+
transfer.assembler.clear();
|
|
245
|
+
}
|
|
246
246
|
this._incoming.delete(transferId);
|
|
247
247
|
this.emit('transferCancelled', { transferId, direction: 'incoming' });
|
|
248
248
|
}
|
|
@@ -250,16 +250,16 @@ class FileManager extends EventEmitter {
|
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* Gets active transfers
|
|
253
|
-
* @returns {
|
|
253
|
+
* @returns {any}
|
|
254
254
|
*/
|
|
255
255
|
getActiveTransfers() {
|
|
256
256
|
return {
|
|
257
|
-
outgoing: Array.from(this._outgoing.values()).map(t => ({
|
|
257
|
+
outgoing: Array.from(this._outgoing.values()).map((/** @type {any} */ t) => ({
|
|
258
258
|
id: t.id, peerId: t.peerId, name: t.meta.name,
|
|
259
259
|
progress: Math.round((t.sentChunks / t.chunks.length) * 100),
|
|
260
260
|
state: t.state
|
|
261
261
|
})),
|
|
262
|
-
incoming: Array.from(this._incoming.values()).map(t => ({
|
|
262
|
+
incoming: Array.from(this._incoming.values()).map((/** @type {any} */ t) => ({
|
|
263
263
|
id: t.meta.id, from: t.senderId, name: t.meta.name,
|
|
264
264
|
progress: t.assembler.progress,
|
|
265
265
|
state: t.meta.state
|
|
@@ -282,7 +282,10 @@ class FileManager extends EventEmitter {
|
|
|
282
282
|
this.removeAllListeners();
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* @param {string} transferId
|
|
287
|
+
* @private
|
|
288
|
+
*/
|
|
286
289
|
_setTransferTimeout(transferId) {
|
|
287
290
|
const timer = setTimeout(() => {
|
|
288
291
|
this.cancelTransfer(transferId);
|
|
@@ -294,7 +297,10 @@ class FileManager extends EventEmitter {
|
|
|
294
297
|
this._timeouts.set(transferId, timer);
|
|
295
298
|
}
|
|
296
299
|
|
|
297
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* @param {string} transferId
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
298
304
|
_clearTransferTimeout(transferId) {
|
|
299
305
|
const timer = this._timeouts.get(transferId);
|
|
300
306
|
if (timer) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* File transfer message types
|
|
10
|
-
* @constant {
|
|
10
|
+
* @constant {any}
|
|
11
11
|
*/
|
|
12
12
|
const FILE_MESSAGE_TYPE = Object.freeze({
|
|
13
13
|
/** Initial file offer with metadata */
|
|
@@ -22,7 +22,7 @@ const FILE_MESSAGE_TYPE = Object.freeze({
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* File transfer states
|
|
25
|
-
* @constant {
|
|
25
|
+
* @constant {any}
|
|
26
26
|
*/
|
|
27
27
|
const FILE_TRANSFER_STATE = Object.freeze({
|
|
28
28
|
PENDING: 'pending',
|
|
@@ -38,14 +38,7 @@ const FILE_TRANSFER_STATE = Object.freeze({
|
|
|
38
38
|
*/
|
|
39
39
|
class FileMessage {
|
|
40
40
|
/**
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {string} options.id - Transfer ID
|
|
43
|
-
* @param {string} options.name - File name
|
|
44
|
-
* @param {string} options.mimeType - MIME type
|
|
45
|
-
* @param {number} options.size - Total size in bytes
|
|
46
|
-
* @param {number} options.totalChunks - Total number of chunks
|
|
47
|
-
* @param {number} [options.chunkSize=4096] - Chunk size in bytes
|
|
48
|
-
* @param {string} [options.senderId] - Sender peer ID
|
|
41
|
+
* @param {any} options
|
|
49
42
|
*/
|
|
50
43
|
constructor(options) {
|
|
51
44
|
this.id = options.id;
|
|
@@ -57,7 +50,9 @@ class FileMessage {
|
|
|
57
50
|
this.senderId = options.senderId || null;
|
|
58
51
|
this.receivedChunks = 0;
|
|
59
52
|
this.state = FILE_TRANSFER_STATE.PENDING;
|
|
53
|
+
/** @type {number | null} */
|
|
60
54
|
this.startedAt = null;
|
|
55
|
+
/** @type {number | null} */
|
|
61
56
|
this.completedAt = null;
|
|
62
57
|
}
|
|
63
58
|
|
|
@@ -82,7 +77,7 @@ class FileMessage {
|
|
|
82
77
|
|
|
83
78
|
/**
|
|
84
79
|
* Serializes the file offer metadata
|
|
85
|
-
* @returns {
|
|
80
|
+
* @returns {any}
|
|
86
81
|
*/
|
|
87
82
|
toOffer() {
|
|
88
83
|
return {
|
|
@@ -98,7 +93,7 @@ class FileMessage {
|
|
|
98
93
|
|
|
99
94
|
/**
|
|
100
95
|
* Creates a FileMessage from an offer
|
|
101
|
-
* @param {
|
|
96
|
+
* @param {any} offer
|
|
102
97
|
* @param {string} senderId
|
|
103
98
|
* @returns {FileMessage}
|
|
104
99
|
*/
|