react-native-ble-mesh 2.1.0 → 2.1.3
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 +9 -2
- 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 +50 -15
- 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 +10 -4
- 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 +19 -26
- 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 +22 -20
- 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 +25 -24
- package/src/transport/adapters/BLEAdapter.js +19 -19
- package/src/transport/adapters/NodeBLEAdapter.js +24 -23
- package/src/transport/adapters/RNBLEAdapter.js +19 -24
- package/src/utils/EventEmitter.js +17 -12
- package/src/utils/LRUCache.js +10 -4
- package/src/utils/RateLimiter.js +1 -1
- package/src/utils/compression.js +6 -8
- 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
|
@@ -10,10 +10,40 @@
|
|
|
10
10
|
|
|
11
11
|
const CryptoProvider = require('../CryptoProvider');
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Converts a hex string to Uint8Array (Buffer-free for React Native compatibility)
|
|
15
|
+
* @param {string} hex - Hex string
|
|
16
|
+
* @returns {Uint8Array}
|
|
17
|
+
*/
|
|
18
|
+
function hexToBytes(hex) {
|
|
19
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
20
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
21
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
22
|
+
}
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Concatenates multiple Uint8Arrays into a single Uint8Array
|
|
28
|
+
* @param {...Uint8Array} arrays
|
|
29
|
+
* @returns {Uint8Array}
|
|
30
|
+
*/
|
|
31
|
+
function concatBytes(...arrays) {
|
|
32
|
+
let totalLength = 0;
|
|
33
|
+
for (const arr of arrays) { totalLength += arr.length; }
|
|
34
|
+
const result = new Uint8Array(totalLength);
|
|
35
|
+
let offset = 0;
|
|
36
|
+
for (const arr of arrays) {
|
|
37
|
+
result.set(arr, offset);
|
|
38
|
+
offset += arr.length;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
13
43
|
/** DER header for PKCS8 private key wrapping (X25519) */
|
|
14
|
-
const PKCS8_HEADER =
|
|
44
|
+
const PKCS8_HEADER = hexToBytes('302e020100300506032b656e04220420');
|
|
15
45
|
/** DER header for SPKI public key wrapping (X25519) */
|
|
16
|
-
const SPKI_HEADER =
|
|
46
|
+
const SPKI_HEADER = hexToBytes('302a300506032b656e032100');
|
|
17
47
|
|
|
18
48
|
/**
|
|
19
49
|
* Crypto provider using react-native-quick-crypto.
|
|
@@ -25,7 +55,9 @@ const SPKI_HEADER = Buffer.from('302a300506032b656e032100', 'hex');
|
|
|
25
55
|
class QuickCryptoProvider extends CryptoProvider {
|
|
26
56
|
constructor(options = {}) {
|
|
27
57
|
super();
|
|
28
|
-
|
|
58
|
+
/** @type {any} */
|
|
59
|
+
const opts = options;
|
|
60
|
+
this._crypto = opts.crypto || null;
|
|
29
61
|
this._nacl = null;
|
|
30
62
|
}
|
|
31
63
|
|
|
@@ -36,6 +68,7 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
36
68
|
_getCrypto() {
|
|
37
69
|
if (!this._crypto) {
|
|
38
70
|
try {
|
|
71
|
+
// @ts-ignore
|
|
39
72
|
this._crypto = require('react-native-quick-crypto');
|
|
40
73
|
} catch (e) {
|
|
41
74
|
throw new Error(
|
|
@@ -57,21 +90,21 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
57
90
|
}
|
|
58
91
|
|
|
59
92
|
/** @inheritdoc */
|
|
60
|
-
sharedSecret(secretKey, publicKey) {
|
|
93
|
+
sharedSecret(/** @type {any} */ secretKey, /** @type {any} */ publicKey) {
|
|
61
94
|
const crypto = this._getCrypto();
|
|
62
95
|
const privKey = crypto.createPrivateKey({
|
|
63
|
-
key:
|
|
96
|
+
key: concatBytes(
|
|
64
97
|
PKCS8_HEADER,
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
new Uint8Array(secretKey)
|
|
99
|
+
),
|
|
67
100
|
format: 'der',
|
|
68
101
|
type: 'pkcs8'
|
|
69
102
|
});
|
|
70
103
|
const pubKey = crypto.createPublicKey({
|
|
71
|
-
key:
|
|
104
|
+
key: concatBytes(
|
|
72
105
|
SPKI_HEADER,
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
new Uint8Array(publicKey)
|
|
107
|
+
),
|
|
75
108
|
format: 'der',
|
|
76
109
|
type: 'spki'
|
|
77
110
|
});
|
|
@@ -81,12 +114,13 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
81
114
|
|
|
82
115
|
/**
|
|
83
116
|
* Lazily loads tweetnacl (cached)
|
|
84
|
-
* @returns {
|
|
117
|
+
* @returns {any} nacl module
|
|
85
118
|
* @private
|
|
86
119
|
*/
|
|
87
120
|
_getNacl() {
|
|
88
121
|
if (!this._nacl) {
|
|
89
122
|
try {
|
|
123
|
+
// @ts-ignore
|
|
90
124
|
this._nacl = require('tweetnacl');
|
|
91
125
|
} catch (e) {
|
|
92
126
|
throw new Error(
|
|
@@ -98,7 +132,7 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
98
132
|
}
|
|
99
133
|
|
|
100
134
|
/** @inheritdoc */
|
|
101
|
-
encrypt(key, nonce, plaintext, _ad) {
|
|
135
|
+
encrypt(/** @type {any} */ key, /** @type {any} */ nonce, /** @type {any} */ plaintext, /** @type {any} */ _ad) {
|
|
102
136
|
// Use tweetnacl for encryption to ensure cross-provider compatibility
|
|
103
137
|
// QuickCrypto's advantage is in fast native key generation (X25519), not AEAD
|
|
104
138
|
const nacl = this._getNacl();
|
|
@@ -114,7 +148,7 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
114
148
|
}
|
|
115
149
|
|
|
116
150
|
/** @inheritdoc */
|
|
117
|
-
decrypt(key, nonce, ciphertext, _ad) {
|
|
151
|
+
decrypt(/** @type {any} */ key, /** @type {any} */ nonce, /** @type {any} */ ciphertext, /** @type {any} */ _ad) {
|
|
118
152
|
const nacl = this._getNacl();
|
|
119
153
|
|
|
120
154
|
// Ensure 24-byte nonce for XSalsa20-Poly1305
|
|
@@ -132,7 +166,7 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
/** @inheritdoc */
|
|
135
|
-
hash(data) {
|
|
169
|
+
hash(/** @type {any} */ data) {
|
|
136
170
|
// Use SHA-512 truncated to 32 bytes for cross-provider compatibility
|
|
137
171
|
const nacl = this._getNacl();
|
|
138
172
|
const full = nacl.hash(data); // SHA-512
|
|
@@ -140,13 +174,14 @@ class QuickCryptoProvider extends CryptoProvider {
|
|
|
140
174
|
}
|
|
141
175
|
|
|
142
176
|
/** @inheritdoc */
|
|
143
|
-
randomBytes(length) {
|
|
177
|
+
randomBytes(/** @type {any} */ length) {
|
|
144
178
|
const crypto = this._getCrypto();
|
|
145
179
|
return new Uint8Array(crypto.randomBytes(length));
|
|
146
180
|
}
|
|
147
181
|
|
|
148
182
|
static isAvailable() {
|
|
149
183
|
try {
|
|
184
|
+
// @ts-ignore
|
|
150
185
|
require('react-native-quick-crypto');
|
|
151
186
|
return true;
|
|
152
187
|
} catch (e) {
|
|
@@ -20,7 +20,7 @@ const CryptoProvider = require('../CryptoProvider');
|
|
|
20
20
|
class TweetNaClProvider extends CryptoProvider {
|
|
21
21
|
/**
|
|
22
22
|
* @param {Object} [options={}]
|
|
23
|
-
* @param {
|
|
23
|
+
* @param {any} [options.nacl] - Injected tweetnacl instance (for testing)
|
|
24
24
|
*/
|
|
25
25
|
constructor(options = {}) {
|
|
26
26
|
super();
|
|
@@ -33,12 +33,13 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Lazily loads tweetnacl
|
|
36
|
-
* @returns {
|
|
36
|
+
* @returns {any} nacl module
|
|
37
37
|
* @private
|
|
38
38
|
*/
|
|
39
39
|
_getNacl() {
|
|
40
40
|
if (!this._nacl) {
|
|
41
41
|
try {
|
|
42
|
+
// @ts-ignore
|
|
42
43
|
this._nacl = require('tweetnacl');
|
|
43
44
|
} catch (e) {
|
|
44
45
|
throw new Error(
|
|
@@ -57,13 +58,13 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/** @inheritdoc */
|
|
60
|
-
sharedSecret(secretKey, publicKey) {
|
|
61
|
+
sharedSecret(/** @type {any} */ secretKey, /** @type {any} */ publicKey) {
|
|
61
62
|
const nacl = this._getNacl();
|
|
62
63
|
return nacl.box.before(publicKey, secretKey);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/** @inheritdoc */
|
|
66
|
-
encrypt(key, nonce, plaintext, _ad) {
|
|
67
|
+
encrypt(/** @type {any} */ key, /** @type {any} */ nonce, /** @type {any} */ plaintext, /** @type {any} */ _ad) {
|
|
67
68
|
const nacl = this._getNacl();
|
|
68
69
|
// tweetnacl uses XSalsa20-Poly1305 with 24-byte nonce
|
|
69
70
|
// nacl.secretbox includes authentication
|
|
@@ -79,7 +80,7 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/** @inheritdoc */
|
|
82
|
-
decrypt(key, nonce, ciphertext, _ad) {
|
|
83
|
+
decrypt(/** @type {any} */ key, /** @type {any} */ nonce, /** @type {any} */ ciphertext, /** @type {any} */ _ad) {
|
|
83
84
|
const nacl = this._getNacl();
|
|
84
85
|
|
|
85
86
|
// Ensure 24-byte nonce (pad short nonces with zeros)
|
|
@@ -94,7 +95,7 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
/** @inheritdoc */
|
|
97
|
-
hash(data) {
|
|
98
|
+
hash(/** @type {any} */ data) {
|
|
98
99
|
const nacl = this._getNacl();
|
|
99
100
|
// tweetnacl provides SHA-512; we return first 32 bytes for SHA-256 compatibility
|
|
100
101
|
const full = nacl.hash(data);
|
|
@@ -102,7 +103,7 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/** @inheritdoc */
|
|
105
|
-
randomBytes(length) {
|
|
106
|
+
randomBytes(/** @type {any} */ length) {
|
|
106
107
|
const nacl = this._getNacl();
|
|
107
108
|
return nacl.randomBytes(length);
|
|
108
109
|
}
|
|
@@ -113,6 +114,7 @@ class TweetNaClProvider extends CryptoProvider {
|
|
|
113
114
|
*/
|
|
114
115
|
static isAvailable() {
|
|
115
116
|
try {
|
|
117
|
+
// @ts-ignore
|
|
116
118
|
require('tweetnacl');
|
|
117
119
|
return true;
|
|
118
120
|
} catch (e) {
|
package/src/errors/AudioError.js
CHANGED
|
@@ -32,7 +32,7 @@ class AudioError extends MeshError {
|
|
|
32
32
|
* @returns {AudioError}
|
|
33
33
|
*/
|
|
34
34
|
static fromCode(code, details = null) {
|
|
35
|
-
const message = ERROR_MESSAGES[code] || 'Audio operation failed';
|
|
35
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || 'Audio operation failed';
|
|
36
36
|
return new AudioError(message, code, details);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -73,6 +73,7 @@ class AudioError extends MeshError {
|
|
|
73
73
|
return new AudioError(
|
|
74
74
|
`Audio session failed with peer ${peerId}`,
|
|
75
75
|
'EA04',
|
|
76
|
+
// @ts-ignore
|
|
76
77
|
{ peerId, ...details }
|
|
77
78
|
);
|
|
78
79
|
}
|
|
@@ -40,7 +40,7 @@ class ConnectionError extends MeshError {
|
|
|
40
40
|
* @returns {ConnectionError} New ConnectionError instance
|
|
41
41
|
*/
|
|
42
42
|
static fromCode(code, peerId = null, details = null) {
|
|
43
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E200;
|
|
43
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E200;
|
|
44
44
|
return new ConnectionError(message, code, peerId, details);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -96,7 +96,7 @@ class ConnectionError extends MeshError {
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Converts error to a JSON-serializable object
|
|
99
|
-
* @returns {
|
|
99
|
+
* @returns {any} JSON representation of the error
|
|
100
100
|
*/
|
|
101
101
|
toJSON() {
|
|
102
102
|
return {
|
|
@@ -32,7 +32,7 @@ class CryptoError extends MeshError {
|
|
|
32
32
|
* @returns {CryptoError} New CryptoError instance
|
|
33
33
|
*/
|
|
34
34
|
static fromCode(code, details = null) {
|
|
35
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E400;
|
|
35
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E400;
|
|
36
36
|
return new CryptoError(message, code, details);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -48,7 +48,7 @@ class HandshakeError extends MeshError {
|
|
|
48
48
|
* @returns {HandshakeError} New HandshakeError instance
|
|
49
49
|
*/
|
|
50
50
|
static fromCode(code, peerId = null, step = null, details = null) {
|
|
51
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E300;
|
|
51
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E300;
|
|
52
52
|
return new HandshakeError(message, code, peerId, step, details);
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -108,7 +108,7 @@ class HandshakeError extends MeshError {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Converts error to a JSON-serializable object
|
|
111
|
-
* @returns {
|
|
111
|
+
* @returns {any} JSON representation of the error
|
|
112
112
|
*/
|
|
113
113
|
toJSON() {
|
|
114
114
|
return {
|
package/src/errors/MeshError.js
CHANGED
|
@@ -37,7 +37,7 @@ class MeshError extends Error {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Additional error details
|
|
40
|
-
* @type {
|
|
40
|
+
* @type {any}
|
|
41
41
|
*/
|
|
42
42
|
this.details = details;
|
|
43
43
|
|
|
@@ -60,7 +60,7 @@ class MeshError extends Error {
|
|
|
60
60
|
* @returns {MeshError} New MeshError instance
|
|
61
61
|
*/
|
|
62
62
|
static fromCode(code, details = null) {
|
|
63
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E900;
|
|
63
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E900;
|
|
64
64
|
return new MeshError(message, code, details);
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -69,12 +69,12 @@ class MeshError extends Error {
|
|
|
69
69
|
* @returns {string} Error type name
|
|
70
70
|
*/
|
|
71
71
|
getTypeName() {
|
|
72
|
-
return ERROR_CODE[this.code] || 'UNKNOWN_ERROR';
|
|
72
|
+
return /** @type {Record<string, string>} */ (ERROR_CODE)[this.code] || 'UNKNOWN_ERROR';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Converts error to a JSON-serializable object
|
|
77
|
-
* @returns {
|
|
77
|
+
* @returns {any} JSON representation of the error
|
|
78
78
|
*/
|
|
79
79
|
toJSON() {
|
|
80
80
|
return {
|
|
@@ -40,7 +40,7 @@ class MessageError extends MeshError {
|
|
|
40
40
|
* @returns {MessageError} New MessageError instance
|
|
41
41
|
*/
|
|
42
42
|
static fromCode(code, messageId = null, details = null) {
|
|
43
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E500;
|
|
43
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E500;
|
|
44
44
|
return new MessageError(message, code, messageId, details);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -116,7 +116,7 @@ class MessageError extends MeshError {
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Converts error to a JSON-serializable object
|
|
119
|
-
* @returns {
|
|
119
|
+
* @returns {any} JSON representation of the error
|
|
120
120
|
*/
|
|
121
121
|
toJSON() {
|
|
122
122
|
return {
|
|
@@ -87,7 +87,7 @@ class ValidationError extends MeshError {
|
|
|
87
87
|
* @returns {ValidationError} New ValidationError instance
|
|
88
88
|
*/
|
|
89
89
|
static fromCode(code, field = null, value = undefined, details = null) {
|
|
90
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES.E800;
|
|
90
|
+
const message = /** @type {Record<string, string>} */ (ERROR_MESSAGES)[code] || /** @type {Record<string, string>} */ (ERROR_MESSAGES).E800;
|
|
91
91
|
return new ValidationError(message, code, field, value, details);
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -127,7 +127,7 @@ class ValidationError extends MeshError {
|
|
|
127
127
|
* Creates an out of range error
|
|
128
128
|
* @param {string} field - Name of the field
|
|
129
129
|
* @param {*} value - The invalid value
|
|
130
|
-
* @param {
|
|
130
|
+
* @param {any} range - Expected range { min, max }
|
|
131
131
|
* @returns {ValidationError} New ValidationError instance
|
|
132
132
|
*/
|
|
133
133
|
static outOfRange(field, value, range) {
|
|
@@ -136,7 +136,7 @@ class ValidationError extends MeshError {
|
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
138
|
* Converts error to a JSON-serializable object
|
|
139
|
-
* @returns {
|
|
139
|
+
* @returns {any} JSON representation of the error
|
|
140
140
|
*/
|
|
141
141
|
toJSON() {
|
|
142
142
|
return {
|
package/src/expo/withBLEMesh.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Default configuration
|
|
25
|
-
* @constant {
|
|
25
|
+
* @constant {any}
|
|
26
26
|
*/
|
|
27
27
|
const DEFAULT_OPTIONS = {
|
|
28
28
|
/** iOS NSBluetoothAlwaysUsageDescription */
|
|
@@ -40,9 +40,9 @@ const DEFAULT_OPTIONS = {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Modifies the iOS Info.plist for BLE permissions.
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
45
|
-
* @returns {
|
|
43
|
+
* @param {any} config - Expo config
|
|
44
|
+
* @param {any} options - Plugin options
|
|
45
|
+
* @returns {any} Modified config
|
|
46
46
|
*/
|
|
47
47
|
function withBLEMeshIOS(config, options) {
|
|
48
48
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -64,9 +64,9 @@ function withBLEMeshIOS(config, options) {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Modifies the Android manifest for BLE permissions.
|
|
67
|
-
* @param {
|
|
68
|
-
* @param {
|
|
69
|
-
* @returns {
|
|
67
|
+
* @param {any} config - Expo config
|
|
68
|
+
* @param {any} options - Plugin options
|
|
69
|
+
* @returns {any} Modified config
|
|
70
70
|
*/
|
|
71
71
|
function withBLEMeshAndroid(config, options) {
|
|
72
72
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -86,9 +86,9 @@ function withBLEMeshAndroid(config, options) {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Main Expo config plugin.
|
|
89
|
-
* @param {
|
|
90
|
-
* @param {
|
|
91
|
-
* @returns {
|
|
89
|
+
* @param {any} config - Expo config
|
|
90
|
+
* @param {any} [options={}] - Plugin options
|
|
91
|
+
* @returns {any} Modified config
|
|
92
92
|
*/
|
|
93
93
|
function withBLEMesh(config, options = {}) {
|
|
94
94
|
config = withBLEMeshIOS(config, options);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
/* global __DEV__ */
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @fileoverview React Native app state management for mesh service
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
class AppStateManager {
|
|
23
24
|
/**
|
|
24
25
|
* Creates a new AppStateManager
|
|
25
|
-
* @param {
|
|
26
|
+
* @param {any} mesh - MeshService instance to manage
|
|
26
27
|
* @param {Object} [options] - Configuration options
|
|
27
28
|
* @param {string} [options.backgroundMode='ULTRA_POWER_SAVER'] - Power mode for background
|
|
28
29
|
* @param {string} [options.foregroundMode='BALANCED'] - Power mode for foreground
|
|
@@ -58,11 +59,12 @@ class AppStateManager {
|
|
|
58
59
|
|
|
59
60
|
// Try to get AppState from React Native
|
|
60
61
|
try {
|
|
62
|
+
// @ts-ignore
|
|
61
63
|
const { AppState } = require('react-native');
|
|
62
64
|
this._AppState = AppState;
|
|
63
65
|
} catch (e) {
|
|
64
66
|
// React Native not available (Node.js environment)
|
|
65
|
-
|
|
67
|
+
// React Native not available (Node.js or test environment) — silently skip
|
|
66
68
|
return false;
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -121,7 +123,9 @@ class AppStateManager {
|
|
|
121
123
|
this._mesh._saveState();
|
|
122
124
|
}
|
|
123
125
|
} catch (e) {
|
|
124
|
-
|
|
126
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
127
|
+
console.warn('AppStateManager: Error handling background transition', e);
|
|
128
|
+
}
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -143,7 +147,9 @@ class AppStateManager {
|
|
|
143
147
|
this._mesh._restoreState();
|
|
144
148
|
}
|
|
145
149
|
} catch (e) {
|
|
146
|
-
|
|
150
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
151
|
+
console.warn('AppStateManager: Error handling foreground transition', e);
|
|
152
|
+
}
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
package/src/hooks/useMesh.js
CHANGED
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
* React hook for managing MeshService lifecycle in React Native apps.
|
|
10
10
|
* Handles initialization, cleanup, and state management.
|
|
11
11
|
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @
|
|
14
|
-
* @param {Object} [config.storage] - Storage adapter
|
|
15
|
-
* @returns {Object} Mesh state and controls
|
|
12
|
+
* @param {any} [config] - MeshService configuration
|
|
13
|
+
* @returns {any} Mesh state and controls
|
|
16
14
|
*
|
|
17
15
|
* @example
|
|
18
16
|
* function App() {
|
|
@@ -35,6 +33,7 @@ function useMesh(config = {}) {
|
|
|
35
33
|
// This hook requires React - check if available
|
|
36
34
|
let React;
|
|
37
35
|
try {
|
|
36
|
+
// @ts-ignore
|
|
38
37
|
React = require('react');
|
|
39
38
|
} catch (e) {
|
|
40
39
|
throw new Error('useMesh requires React. Install react as a dependency.');
|
|
@@ -65,7 +64,7 @@ function useMesh(config = {}) {
|
|
|
65
64
|
}, [config.displayName]);
|
|
66
65
|
|
|
67
66
|
// Initialize mesh
|
|
68
|
-
const initialize = useCallback(async (transport) => {
|
|
67
|
+
const initialize = useCallback(async (/** @type {any} */ transport) => {
|
|
69
68
|
try {
|
|
70
69
|
if (!mountedRef.current) { return; }
|
|
71
70
|
setState('initializing');
|
|
@@ -82,13 +81,14 @@ function useMesh(config = {}) {
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
// Setup state change listener
|
|
84
|
+
// @ts-ignore
|
|
85
85
|
stateHandlerRef.current = ({ newState }) => {
|
|
86
86
|
if (mountedRef.current) {
|
|
87
87
|
setState(newState);
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
errorHandlerRef.current = (err) => {
|
|
91
|
+
errorHandlerRef.current = (/** @type {any} */ err) => {
|
|
92
92
|
if (mountedRef.current) {
|
|
93
93
|
setError(err);
|
|
94
94
|
}
|
|
@@ -118,7 +118,7 @@ function useMesh(config = {}) {
|
|
|
118
118
|
}, [getMesh, config.storage]);
|
|
119
119
|
|
|
120
120
|
// Start with transport
|
|
121
|
-
const start = useCallback(async (transport) => {
|
|
121
|
+
const start = useCallback(async (/** @type {any} */ transport) => {
|
|
122
122
|
const mesh = getMesh();
|
|
123
123
|
try {
|
|
124
124
|
await mesh.start(transport);
|
package/src/hooks/useMessages.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* React hook for sending and receiving messages in the mesh network.
|
|
10
10
|
* Manages message state and provides send functions.
|
|
11
11
|
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @param {
|
|
14
|
-
|
|
15
|
-
* @returns {
|
|
12
|
+
* @param {any} mesh - MeshService instance
|
|
13
|
+
* @param {any} [options] - Options
|
|
14
|
+
*
|
|
15
|
+
* @returns {any} Messages state and send functions
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* function Chat({ mesh, peerId }) {
|
|
@@ -38,6 +38,7 @@ function useMessages(mesh, options = {}) {
|
|
|
38
38
|
// This hook requires React
|
|
39
39
|
let React;
|
|
40
40
|
try {
|
|
41
|
+
// @ts-ignore
|
|
41
42
|
React = require('react');
|
|
42
43
|
} catch (e) {
|
|
43
44
|
throw new Error('useMessages requires React. Install react as a dependency.');
|
|
@@ -52,11 +53,11 @@ function useMessages(mesh, options = {}) {
|
|
|
52
53
|
const messageIdRef = useRef(new Set());
|
|
53
54
|
|
|
54
55
|
// Add message to state (with dedup)
|
|
55
|
-
const addMessage = useCallback((msg) => {
|
|
56
|
+
const addMessage = useCallback((/** @type {any} */ msg) => {
|
|
56
57
|
if (messageIdRef.current.has(msg.id)) { return; }
|
|
57
58
|
messageIdRef.current.add(msg.id);
|
|
58
59
|
|
|
59
|
-
setMessages(prev => {
|
|
60
|
+
setMessages((/** @type {any} */ prev) => {
|
|
60
61
|
const updated = [msg, ...prev];
|
|
61
62
|
if (updated.length > maxMessages) {
|
|
62
63
|
for (let i = maxMessages; i < updated.length; i++) {
|
|
@@ -72,7 +73,7 @@ function useMessages(mesh, options = {}) {
|
|
|
72
73
|
useEffect(() => {
|
|
73
74
|
if (!mesh) { return; }
|
|
74
75
|
|
|
75
|
-
const handleBroadcast = (data) => {
|
|
76
|
+
const handleBroadcast = (/** @type {any} */ data) => {
|
|
76
77
|
addMessage({
|
|
77
78
|
id: data.messageId,
|
|
78
79
|
type: 'broadcast',
|
|
@@ -83,7 +84,7 @@ function useMessages(mesh, options = {}) {
|
|
|
83
84
|
});
|
|
84
85
|
};
|
|
85
86
|
|
|
86
|
-
const handlePrivate = (data) => {
|
|
87
|
+
const handlePrivate = (/** @type {any} */ data) => {
|
|
87
88
|
addMessage({
|
|
88
89
|
id: data.messageId,
|
|
89
90
|
type: 'private',
|
|
@@ -94,7 +95,7 @@ function useMessages(mesh, options = {}) {
|
|
|
94
95
|
});
|
|
95
96
|
};
|
|
96
97
|
|
|
97
|
-
const handleChannel = (data) => {
|
|
98
|
+
const handleChannel = (/** @type {any} */ data) => {
|
|
98
99
|
addMessage({
|
|
99
100
|
id: data.messageId,
|
|
100
101
|
type: 'channel',
|
|
@@ -120,7 +121,7 @@ function useMessages(mesh, options = {}) {
|
|
|
120
121
|
}, [mesh, addMessage]);
|
|
121
122
|
|
|
122
123
|
// Send broadcast message
|
|
123
|
-
const sendBroadcast = useCallback((content) => {
|
|
124
|
+
const sendBroadcast = useCallback((/** @type {any} */ content) => {
|
|
124
125
|
if (!mesh) { throw new Error('Mesh not initialized'); }
|
|
125
126
|
setError(null);
|
|
126
127
|
|
|
@@ -142,7 +143,7 @@ function useMessages(mesh, options = {}) {
|
|
|
142
143
|
}, [mesh, addMessage]);
|
|
143
144
|
|
|
144
145
|
// Send private message
|
|
145
|
-
const sendPrivate = useCallback(async (peerId, content) => {
|
|
146
|
+
const sendPrivate = useCallback(async (/** @type {any} */ peerId, /** @type {any} */ content) => {
|
|
146
147
|
if (!mesh) { throw new Error('Mesh not initialized'); }
|
|
147
148
|
setError(null);
|
|
148
149
|
setSending(true);
|
|
@@ -168,7 +169,7 @@ function useMessages(mesh, options = {}) {
|
|
|
168
169
|
}, [mesh, addMessage]);
|
|
169
170
|
|
|
170
171
|
// Send channel message
|
|
171
|
-
const sendToChannel = useCallback((channelId, content) => {
|
|
172
|
+
const sendToChannel = useCallback((/** @type {any} */ channelId, /** @type {any} */ content) => {
|
|
172
173
|
if (!mesh) { throw new Error('Mesh not initialized'); }
|
|
173
174
|
setError(null);
|
|
174
175
|
|
package/src/hooks/usePeers.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* React hook for managing and observing peers in the mesh network.
|
|
10
10
|
* Automatically updates when peers connect, disconnect, or change state.
|
|
11
11
|
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @returns {
|
|
12
|
+
* @param {any} mesh - MeshService instance
|
|
13
|
+
* @returns {any} Peers state and utilities
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* function PeerList({ mesh }) {
|
|
@@ -30,6 +30,7 @@ function usePeers(mesh) {
|
|
|
30
30
|
// This hook requires React
|
|
31
31
|
let React;
|
|
32
32
|
try {
|
|
33
|
+
// @ts-ignore
|
|
33
34
|
React = require('react');
|
|
34
35
|
} catch (e) {
|
|
35
36
|
throw new Error('usePeers requires React. Install react as a dependency.');
|
|
@@ -45,8 +46,8 @@ function usePeers(mesh) {
|
|
|
45
46
|
if (mesh) {
|
|
46
47
|
try {
|
|
47
48
|
const allPeers = mesh.getPeers();
|
|
48
|
-
setPeers(prev => {
|
|
49
|
-
if (prev.length === allPeers.length && prev.every((p, i) => p.id === allPeers[i]?.id && p.connectionState === allPeers[i]?.connectionState)) {
|
|
49
|
+
setPeers((/** @type {any} */ prev) => {
|
|
50
|
+
if (prev.length === allPeers.length && prev.every((/** @type {any} */ p, /** @type {any} */ i) => p.id === allPeers[i]?.id && p.connectionState === allPeers[i]?.connectionState)) {
|
|
50
51
|
return prev;
|
|
51
52
|
}
|
|
52
53
|
return allPeers;
|
|
@@ -82,19 +83,19 @@ function usePeers(mesh) {
|
|
|
82
83
|
|
|
83
84
|
// Computed values
|
|
84
85
|
const connectedPeers = useMemo(() => {
|
|
85
|
-
return peers.filter(p => p.connectionState === 'connected' || p.connectionState === 'secured');
|
|
86
|
+
return peers.filter((/** @type {any} */ p) => p.connectionState === 'connected' || p.connectionState === 'secured');
|
|
86
87
|
}, [peers]);
|
|
87
88
|
|
|
88
89
|
const securedPeers = useMemo(() => {
|
|
89
|
-
return peers.filter(p => p.connectionState === 'secured');
|
|
90
|
+
return peers.filter((/** @type {any} */ p) => p.connectionState === 'secured');
|
|
90
91
|
}, [peers]);
|
|
91
92
|
|
|
92
93
|
// Get single peer by ID (O(1) lookup via peerMap)
|
|
93
|
-
const peerMap = useMemo(() => new Map(peers.map(p => [p.id, p])), [peers]);
|
|
94
|
-
const getPeer = useCallback((peerId) => peerMap.get(peerId), [peerMap]);
|
|
94
|
+
const peerMap = useMemo(() => new Map(peers.map((/** @type {any} */ p) => [p.id, p])), [peers]);
|
|
95
|
+
const getPeer = useCallback((/** @type {any} */ peerId) => peerMap.get(peerId), [peerMap]);
|
|
95
96
|
|
|
96
97
|
// Check if peer is connected
|
|
97
|
-
const isConnected = useCallback((peerId) => {
|
|
98
|
+
const isConnected = useCallback((/** @type {any} */ peerId) => {
|
|
98
99
|
const peer = getPeer(peerId);
|
|
99
100
|
return peer && (peer.connectionState === 'connected' || peer.connectionState === 'secured');
|
|
100
101
|
}, [getPeer]);
|