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
|
@@ -21,14 +21,14 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
21
21
|
/**
|
|
22
22
|
* Creates a new RNBLEAdapter instance
|
|
23
23
|
* @param {Object} [options={}] - Adapter options
|
|
24
|
-
* @param {
|
|
24
|
+
* @param {any} [options.BleManager] - BleManager class from react-native-ble-plx
|
|
25
25
|
*/
|
|
26
26
|
constructor(options = {}) {
|
|
27
27
|
super(options);
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* BleManager instance
|
|
31
|
-
* @type {
|
|
31
|
+
* @type {any}
|
|
32
32
|
* @private
|
|
33
33
|
*/
|
|
34
34
|
this._manager = null;
|
|
@@ -45,32 +45,33 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
45
45
|
* @type {string|null}
|
|
46
46
|
* @private
|
|
47
47
|
*/
|
|
48
|
+
// @ts-ignore
|
|
48
49
|
this._restoreIdentifier = options.restoreIdentifier || null;
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Connected devices map
|
|
52
|
-
* @type {Map<string,
|
|
53
|
+
* @type {Map<string, any>}
|
|
53
54
|
* @private
|
|
54
55
|
*/
|
|
55
56
|
this._devices = new Map();
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Subscription handlers map
|
|
59
|
-
* @type {Map<string,
|
|
60
|
+
* @type {Map<string, any>}
|
|
60
61
|
* @private
|
|
61
62
|
*/
|
|
62
63
|
this._subscriptions = new Map();
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Scan subscription reference
|
|
66
|
-
* @type {
|
|
67
|
+
* @type {any}
|
|
67
68
|
* @private
|
|
68
69
|
*/
|
|
69
70
|
this._scanSubscription = null;
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* State subscription reference
|
|
73
|
-
* @type {
|
|
74
|
+
* @type {any}
|
|
74
75
|
* @private
|
|
75
76
|
*/
|
|
76
77
|
this._stateSubscription = null;
|
|
@@ -96,6 +97,7 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
96
97
|
// Try to load BleManager if not provided
|
|
97
98
|
if (!this._BleManager) {
|
|
98
99
|
try {
|
|
100
|
+
// @ts-ignore
|
|
99
101
|
const blePlx = require('react-native-ble-plx');
|
|
100
102
|
this._BleManager = blePlx.BleManager;
|
|
101
103
|
} catch (error) {
|
|
@@ -108,7 +110,7 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
108
110
|
const managerOptions = {};
|
|
109
111
|
if (this._restoreIdentifier) {
|
|
110
112
|
managerOptions.restoreStateIdentifier = this._restoreIdentifier;
|
|
111
|
-
managerOptions.restoreStateFunction = (restoredState) => {
|
|
113
|
+
managerOptions.restoreStateFunction = (/** @type {any} */ restoredState) => {
|
|
112
114
|
// Re-populate devices from restored state
|
|
113
115
|
if (restoredState && restoredState.connectedPeripherals) {
|
|
114
116
|
for (const peripheral of restoredState.connectedPeripherals) {
|
|
@@ -117,10 +119,11 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
117
119
|
}
|
|
118
120
|
};
|
|
119
121
|
}
|
|
122
|
+
// @ts-ignore
|
|
120
123
|
this._manager = new this._BleManager(managerOptions);
|
|
121
124
|
|
|
122
125
|
// Subscribe to state changes
|
|
123
|
-
this._stateSubscription = this._manager.onStateChange((state) => {
|
|
126
|
+
this._stateSubscription = this._manager.onStateChange((/** @type {any} */ state) => {
|
|
124
127
|
this._notifyStateChange(this._mapState(state));
|
|
125
128
|
}, true);
|
|
126
129
|
|
|
@@ -174,7 +177,7 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
174
177
|
async startScan(serviceUUIDs, callback) {
|
|
175
178
|
this._ensureInitialized();
|
|
176
179
|
|
|
177
|
-
this._manager.startDeviceScan(serviceUUIDs, null, (error, device) => {
|
|
180
|
+
this._manager.startDeviceScan(serviceUUIDs, null, (/** @type {any} */ error, /** @type {any} */ device) => {
|
|
178
181
|
if (error) {
|
|
179
182
|
return;
|
|
180
183
|
}
|
|
@@ -292,7 +295,7 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
292
295
|
deviceId,
|
|
293
296
|
serviceUUID,
|
|
294
297
|
charUUID,
|
|
295
|
-
(error, characteristic) => {
|
|
298
|
+
(/** @type {any} */ error, /** @type {any} */ characteristic) => {
|
|
296
299
|
if (!error && characteristic) {
|
|
297
300
|
const data = this._base64ToUint8Array(characteristic.value);
|
|
298
301
|
callback(data);
|
|
@@ -328,7 +331,7 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
328
331
|
PoweredOff: BLEAdapter.STATE.POWERED_OFF,
|
|
329
332
|
PoweredOn: BLEAdapter.STATE.POWERED_ON
|
|
330
333
|
};
|
|
331
|
-
return stateMap[state] || BLEAdapter.STATE.UNKNOWN;
|
|
334
|
+
return /** @type {any} */ (stateMap)[state] || BLEAdapter.STATE.UNKNOWN;
|
|
332
335
|
}
|
|
333
336
|
|
|
334
337
|
/**
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
class EventEmitter {
|
|
13
13
|
/**
|
|
14
14
|
* Creates a new EventEmitter
|
|
15
|
-
* @param {
|
|
16
|
-
*
|
|
15
|
+
* @param {any} [options] - Configuration options
|
|
16
|
+
*
|
|
17
17
|
*/
|
|
18
18
|
constructor(options = {}) {
|
|
19
19
|
/**
|
|
@@ -47,6 +47,7 @@ class EventEmitter {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const listeners = this._events.get(event);
|
|
50
|
+
if (!listeners) { return this; }
|
|
50
51
|
|
|
51
52
|
// Warn if max listeners exceeded
|
|
52
53
|
if (listeners.length >= this._maxListeners) {
|
|
@@ -76,7 +77,7 @@ class EventEmitter {
|
|
|
76
77
|
this._events.set(event, []);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
this._events.get(event)
|
|
80
|
+
this._events.get(event)?.push({ listener, once: true });
|
|
80
81
|
return this;
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -92,13 +93,13 @@ class EventEmitter {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const listeners = this._events.get(event);
|
|
95
|
-
const index = listeners
|
|
96
|
+
const index = listeners?.findIndex(entry => entry.listener === listener) ?? -1;
|
|
96
97
|
|
|
97
98
|
if (index !== -1) {
|
|
98
|
-
listeners
|
|
99
|
+
listeners?.splice(index, 1);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
if (listeners
|
|
102
|
+
if (listeners?.length === 0) {
|
|
102
103
|
this._events.delete(event);
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -116,10 +117,12 @@ class EventEmitter {
|
|
|
116
117
|
return false;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
const listeners = this._events.get(event)
|
|
120
|
-
|
|
120
|
+
const listeners = this._events.get(event);
|
|
121
|
+
if (!listeners) { return false; }
|
|
122
|
+
const hasOnce = listeners.some(e => e.once);
|
|
123
|
+
const iterList = hasOnce ? listeners.slice() : listeners;
|
|
121
124
|
|
|
122
|
-
for (const entry of
|
|
125
|
+
for (const entry of iterList) {
|
|
123
126
|
try {
|
|
124
127
|
entry.listener.apply(this, args);
|
|
125
128
|
} catch (error) {
|
|
@@ -130,15 +133,11 @@ class EventEmitter {
|
|
|
130
133
|
console.error('Error in error handler:', error);
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
|
-
|
|
134
|
-
if (entry.once) {
|
|
135
|
-
toRemove.push(entry);
|
|
136
|
-
}
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
// Remove one-time listeners
|
|
140
|
-
if (
|
|
141
|
-
const remaining =
|
|
139
|
+
if (hasOnce) {
|
|
140
|
+
const remaining = listeners.filter(e => !e.once);
|
|
142
141
|
if (remaining.length === 0) {
|
|
143
142
|
this._events.delete(event);
|
|
144
143
|
} else {
|
|
@@ -172,7 +171,7 @@ class EventEmitter {
|
|
|
172
171
|
if (!this._events.has(event)) {
|
|
173
172
|
return 0;
|
|
174
173
|
}
|
|
175
|
-
return this._events.get(event)
|
|
174
|
+
return this._events.get(event)?.length ?? 0;
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
/**
|
package/src/utils/LRUCache.js
CHANGED
|
@@ -47,7 +47,7 @@ class LRUCache {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Move to end (most recently used)
|
|
50
|
-
const value = this._cache.get(key);
|
|
50
|
+
const value = /** @type {V} */ (this._cache.get(key));
|
|
51
51
|
this._cache.delete(key);
|
|
52
52
|
this._cache.set(key, value);
|
|
53
53
|
|
|
@@ -67,7 +67,9 @@ class LRUCache {
|
|
|
67
67
|
} else if (this._cache.size >= this._maxSize) {
|
|
68
68
|
// Remove least recently used (first item)
|
|
69
69
|
const firstKey = this._cache.keys().next().value;
|
|
70
|
-
|
|
70
|
+
if (firstKey !== undefined) {
|
|
71
|
+
this._cache.delete(firstKey);
|
|
72
|
+
}
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
this._cache.set(key, value);
|
|
@@ -134,7 +136,7 @@ class LRUCache {
|
|
|
134
136
|
|
|
135
137
|
/**
|
|
136
138
|
* Returns all entries in the cache (most recent last)
|
|
137
|
-
* @returns {
|
|
139
|
+
* @returns {any[]} Array of [key, value] pairs
|
|
138
140
|
*/
|
|
139
141
|
entries() {
|
|
140
142
|
return Array.from(this._cache.entries());
|
|
@@ -194,7 +196,11 @@ class LRUCache {
|
|
|
194
196
|
// Evict oldest items if over new limit
|
|
195
197
|
while (this._cache.size > this._maxSize) {
|
|
196
198
|
const firstKey = this._cache.keys().next().value;
|
|
197
|
-
|
|
199
|
+
if (firstKey !== undefined) {
|
|
200
|
+
this._cache.delete(firstKey);
|
|
201
|
+
} else {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
}
|
package/src/utils/RateLimiter.js
CHANGED
package/src/utils/bytes.js
CHANGED
|
@@ -11,9 +11,16 @@
|
|
|
11
11
|
* @returns {Uint8Array} Concatenated array
|
|
12
12
|
*/
|
|
13
13
|
function concat(...arrays) {
|
|
14
|
-
//
|
|
15
|
-
const validArrays =
|
|
16
|
-
|
|
14
|
+
// Single-pass: filter out undefined/null and calculate total length
|
|
15
|
+
const validArrays = [];
|
|
16
|
+
let totalLength = 0;
|
|
17
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
18
|
+
const arr = arrays[i];
|
|
19
|
+
if (arr !== null && arr !== undefined) {
|
|
20
|
+
validArrays.push(arr);
|
|
21
|
+
totalLength += arr.length;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
const result = new Uint8Array(totalLength);
|
|
19
26
|
let offset = 0;
|
|
@@ -119,10 +126,7 @@ function xor(a, b) {
|
|
|
119
126
|
* @returns {Uint8Array} The filled array (same reference)
|
|
120
127
|
*/
|
|
121
128
|
function fill(array, value) {
|
|
122
|
-
|
|
123
|
-
for (let i = 0; i < array.length; i++) {
|
|
124
|
-
array[i] = byte;
|
|
125
|
-
}
|
|
129
|
+
array.fill(value & 0xff);
|
|
126
130
|
return array;
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -132,9 +136,7 @@ function fill(array, value) {
|
|
|
132
136
|
* @returns {Uint8Array} Copy of the array
|
|
133
137
|
*/
|
|
134
138
|
function copy(array) {
|
|
135
|
-
|
|
136
|
-
result.set(array);
|
|
137
|
-
return result;
|
|
139
|
+
return array.slice();
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
/**
|
package/src/utils/compression.js
CHANGED
|
@@ -15,7 +15,7 @@ const { ValidationError, MeshError } = require('../errors');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Default compression configuration
|
|
18
|
-
* @constant {
|
|
18
|
+
* @constant {any}
|
|
19
19
|
*/
|
|
20
20
|
const DEFAULT_CONFIG = Object.freeze({
|
|
21
21
|
/** Minimum payload size to compress (bytes) */
|
|
@@ -45,8 +45,8 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
45
45
|
class MessageCompressor {
|
|
46
46
|
/**
|
|
47
47
|
* Creates a new MessageCompressor instance.
|
|
48
|
-
* @param {
|
|
49
|
-
|
|
48
|
+
* @param {any} [options={}] - Compression options
|
|
49
|
+
*
|
|
50
50
|
*/
|
|
51
51
|
constructor(options = {}) {
|
|
52
52
|
this._config = { ...DEFAULT_CONFIG, ...options };
|
|
@@ -57,6 +57,9 @@ class MessageCompressor {
|
|
|
57
57
|
bytesIn: 0,
|
|
58
58
|
bytesOut: 0
|
|
59
59
|
};
|
|
60
|
+
|
|
61
|
+
// Pre-allocate hash table to avoid per-call allocation
|
|
62
|
+
this._hashTable = new Int32Array(this._config.hashTableSize);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
/**
|
|
@@ -86,7 +89,7 @@ class MessageCompressor {
|
|
|
86
89
|
this._stats.bytesOut += compressed.length;
|
|
87
90
|
return { data: compressed, compressed: true };
|
|
88
91
|
}
|
|
89
|
-
} catch (error) {
|
|
92
|
+
} catch (/** @type {any} */ error) {
|
|
90
93
|
// Log compression error at debug level for troubleshooting
|
|
91
94
|
if (typeof console !== 'undefined' && console.debug) {
|
|
92
95
|
console.debug('Compression failed, using uncompressed:', error.message);
|
|
@@ -129,7 +132,7 @@ class MessageCompressor {
|
|
|
129
132
|
|
|
130
133
|
/**
|
|
131
134
|
* Gets compression statistics.
|
|
132
|
-
* @returns {
|
|
135
|
+
* @returns {any} Statistics
|
|
133
136
|
*/
|
|
134
137
|
getStats() {
|
|
135
138
|
const ratio = this._stats.bytesIn > 0
|
|
@@ -177,9 +180,8 @@ class MessageCompressor {
|
|
|
177
180
|
output[outputPos++] = (inputLen >> 16) & 0xff;
|
|
178
181
|
output[outputPos++] = (inputLen >> 24) & 0xff;
|
|
179
182
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
const hashTable = new Int32Array(this._config.hashTableSize);
|
|
183
|
+
// Reuse pre-allocated hash table, reset for this call
|
|
184
|
+
const hashTable = this._hashTable;
|
|
183
185
|
hashTable.fill(-1);
|
|
184
186
|
|
|
185
187
|
let anchor = 0;
|
package/src/utils/encoding.js
CHANGED
|
@@ -14,18 +14,47 @@ for (let i = 0; i < 16; i++) {
|
|
|
14
14
|
HEX_LOOKUP[HEX_CHARS.toUpperCase().charCodeAt(i)] = i;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Pre-computed byte-to-hex lookup table (avoids per-byte toString + padStart)
|
|
18
|
+
const HEX_TABLE = new Array(256);
|
|
19
|
+
for (let i = 0; i < 256; i++) {
|
|
20
|
+
HEX_TABLE[i] = HEX_CHARS[i >> 4] + HEX_CHARS[i & 0x0f];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Cached TextEncoder/TextDecoder singletons (avoid per-call allocation)
|
|
24
|
+
/** @type {any} */ let _cachedEncoder = null;
|
|
25
|
+
/** @type {any} */ let _cachedDecoder = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @returns {any}
|
|
29
|
+
*/
|
|
30
|
+
function _getEncoder() {
|
|
31
|
+
if (!_cachedEncoder && typeof TextEncoder !== 'undefined') {
|
|
32
|
+
_cachedEncoder = new TextEncoder();
|
|
33
|
+
}
|
|
34
|
+
return _cachedEncoder;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @returns {any}
|
|
39
|
+
*/
|
|
40
|
+
function _getDecoder() {
|
|
41
|
+
if (!_cachedDecoder && typeof TextDecoder !== 'undefined') {
|
|
42
|
+
_cachedDecoder = new TextDecoder();
|
|
43
|
+
}
|
|
44
|
+
return _cachedDecoder;
|
|
45
|
+
}
|
|
46
|
+
|
|
17
47
|
/**
|
|
18
48
|
* Converts a byte array to a hexadecimal string
|
|
19
49
|
* @param {Uint8Array} bytes - Bytes to convert
|
|
20
50
|
* @returns {string} Hexadecimal string
|
|
21
51
|
*/
|
|
22
52
|
function bytesToHex(bytes) {
|
|
23
|
-
|
|
53
|
+
const parts = new Array(bytes.length);
|
|
24
54
|
for (let i = 0; i < bytes.length; i++) {
|
|
25
|
-
|
|
26
|
-
result += HEX_CHARS[bytes[i] & 0x0f];
|
|
55
|
+
parts[i] = HEX_TABLE[bytes[i]];
|
|
27
56
|
}
|
|
28
|
-
return
|
|
57
|
+
return parts.join('');
|
|
29
58
|
}
|
|
30
59
|
|
|
31
60
|
/**
|
|
@@ -62,8 +91,9 @@ function hexToBytes(hex) {
|
|
|
62
91
|
* @returns {Uint8Array} UTF-8 encoded bytes
|
|
63
92
|
*/
|
|
64
93
|
function stringToBytes(str) {
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
const encoder = _getEncoder();
|
|
95
|
+
if (encoder) {
|
|
96
|
+
return encoder.encode(str);
|
|
67
97
|
}
|
|
68
98
|
|
|
69
99
|
// Fallback for environments without TextEncoder
|
|
@@ -99,8 +129,9 @@ function stringToBytes(str) {
|
|
|
99
129
|
* @returns {string} Decoded string
|
|
100
130
|
*/
|
|
101
131
|
function bytesToString(bytes) {
|
|
102
|
-
|
|
103
|
-
|
|
132
|
+
const decoder = _getDecoder();
|
|
133
|
+
if (decoder) {
|
|
134
|
+
return decoder.decode(bytes);
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
// Fallback for environments without TextDecoder
|
package/src/utils/retry.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Default retry options
|
|
10
|
-
* @constant {
|
|
10
|
+
* @constant {any}
|
|
11
11
|
*/
|
|
12
12
|
const DEFAULT_OPTIONS = {
|
|
13
13
|
maxRetries: 3,
|
|
@@ -21,7 +21,7 @@ const DEFAULT_OPTIONS = {
|
|
|
21
21
|
/**
|
|
22
22
|
* Calculates delay with optional jitter
|
|
23
23
|
* @param {number} attempt - Current attempt number (0-based)
|
|
24
|
-
* @param {
|
|
24
|
+
* @param {any} options - Retry options
|
|
25
25
|
* @returns {number} Delay in milliseconds
|
|
26
26
|
* @private
|
|
27
27
|
*/
|
|
@@ -46,13 +46,10 @@ function calculateDelay(attempt, options) {
|
|
|
46
46
|
* Retries an async function with exponential backoff
|
|
47
47
|
* @template T
|
|
48
48
|
* @param {function(): Promise<T>} fn - Async function to retry
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {number} [options.initialDelay=100] - Initial delay in milliseconds
|
|
52
|
-
* @param {number} [options.maxDelay=10000] - Maximum delay in milliseconds
|
|
53
|
-
* @param {number} [options.factor=2] - Exponential factor
|
|
54
|
-
* @param {boolean} [options.jitter=true] - Add randomness to delays
|
|
49
|
+
* @param {any} [options] - Retry options *
|
|
50
|
+
* @param {object} options
|
|
55
51
|
* @param {function(Error, number): boolean} [options.shouldRetry] - Predicate to determine if should retry
|
|
52
|
+
* @param {object} options
|
|
56
53
|
* @param {function(Error, number): void} [options.onRetry] - Callback on each retry
|
|
57
54
|
* @returns {Promise<T>} Result of the function
|
|
58
55
|
* @throws {Error} Last error if all retries fail
|
|
@@ -66,7 +63,7 @@ async function retry(fn, options = {}) {
|
|
|
66
63
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
67
64
|
try {
|
|
68
65
|
return await fn();
|
|
69
|
-
} catch (error) {
|
|
66
|
+
} catch (/** @type {any} */ error) {
|
|
70
67
|
lastError = error;
|
|
71
68
|
|
|
72
69
|
// Check if we should retry
|
|
@@ -94,11 +91,12 @@ async function retry(fn, options = {}) {
|
|
|
94
91
|
* Creates a retryable version of an async function
|
|
95
92
|
* @template T
|
|
96
93
|
* @param {function(...*): Promise<T>} fn - Async function to wrap
|
|
97
|
-
* @param {
|
|
94
|
+
* @param {any} [options] - Retry options
|
|
98
95
|
* @returns {function(...*): Promise<T>} Wrapped function with retry logic
|
|
99
96
|
*/
|
|
100
97
|
function retryable(fn, options = {}) {
|
|
101
98
|
return function retryableFn(...args) {
|
|
99
|
+
// @ts-ignore
|
|
102
100
|
return retry(() => fn.apply(this, args), options);
|
|
103
101
|
};
|
|
104
102
|
}
|
|
@@ -109,7 +107,7 @@ function retryable(fn, options = {}) {
|
|
|
109
107
|
* @returns {function(Error): boolean} Predicate function
|
|
110
108
|
*/
|
|
111
109
|
function retryOn(errorTypes) {
|
|
112
|
-
return (error) => {
|
|
110
|
+
return (/** @type {any} */ error) => {
|
|
113
111
|
return errorTypes.some(ErrorType => error instanceof ErrorType);
|
|
114
112
|
};
|
|
115
113
|
}
|
|
@@ -120,7 +118,7 @@ function retryOn(errorTypes) {
|
|
|
120
118
|
* @returns {function(Error): boolean} Predicate function
|
|
121
119
|
*/
|
|
122
120
|
function retryExcept(errorTypes) {
|
|
123
|
-
return (error) => {
|
|
121
|
+
return (/** @type {any} */ error) => {
|
|
124
122
|
return !errorTypes.some(ErrorType => error instanceof ErrorType);
|
|
125
123
|
};
|
|
126
124
|
}
|
|
@@ -131,7 +129,7 @@ function retryExcept(errorTypes) {
|
|
|
131
129
|
* @returns {function(Error): boolean} Predicate function
|
|
132
130
|
*/
|
|
133
131
|
function retryOnCodes(codes) {
|
|
134
|
-
return (error) => {
|
|
132
|
+
return (/** @type {any} */ error) => {
|
|
135
133
|
return error.code && codes.includes(error.code);
|
|
136
134
|
};
|
|
137
135
|
}
|
package/src/utils/time.js
CHANGED
|
@@ -23,8 +23,9 @@ function delay(ms) {
|
|
|
23
23
|
* @returns {Promise<T>} Promise that rejects on timeout
|
|
24
24
|
*/
|
|
25
25
|
function withTimeout(promise, ms, message = 'Operation timed out') {
|
|
26
|
-
let timeoutId;
|
|
26
|
+
/** @type {any} */ let timeoutId;
|
|
27
27
|
|
|
28
|
+
/** @type {Promise<T>} */
|
|
28
29
|
const timeoutPromise = new Promise((_, reject) => {
|
|
29
30
|
timeoutId = setTimeout(() => {
|
|
30
31
|
reject(new Error(message));
|
|
@@ -32,10 +33,10 @@ function withTimeout(promise, ms, message = 'Operation timed out') {
|
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
return Promise.race([
|
|
35
|
-
promise.then(result => {
|
|
36
|
+
promise.then((/** @type {T} */ result) => {
|
|
36
37
|
clearTimeout(timeoutId);
|
|
37
38
|
return result;
|
|
38
|
-
}).catch(error => {
|
|
39
|
+
}).catch((/** @type {any} */ error) => {
|
|
39
40
|
clearTimeout(timeoutId);
|
|
40
41
|
throw error;
|
|
41
42
|
}),
|
|
@@ -57,7 +58,9 @@ function now() {
|
|
|
57
58
|
* @returns {number} High-resolution timestamp
|
|
58
59
|
*/
|
|
59
60
|
function hrTime() {
|
|
61
|
+
// @ts-ignore - performance may be available in some environments
|
|
60
62
|
if (typeof performance !== 'undefined' && performance.now) {
|
|
63
|
+
// @ts-ignore
|
|
61
64
|
return performance.now();
|
|
62
65
|
}
|
|
63
66
|
return Date.now();
|
|
@@ -119,7 +122,7 @@ function formatDuration(ms) {
|
|
|
119
122
|
* @returns {function(...*): void} Debounced function
|
|
120
123
|
*/
|
|
121
124
|
function debounce(fn, waitMs) {
|
|
122
|
-
let timeoutId = null;
|
|
125
|
+
/** @type {any} */ let timeoutId = null;
|
|
123
126
|
|
|
124
127
|
return function debounced(...args) {
|
|
125
128
|
if (timeoutId !== null) {
|
|
@@ -128,6 +131,7 @@ function debounce(fn, waitMs) {
|
|
|
128
131
|
|
|
129
132
|
timeoutId = setTimeout(() => {
|
|
130
133
|
timeoutId = null;
|
|
134
|
+
// @ts-ignore
|
|
131
135
|
fn.apply(this, args);
|
|
132
136
|
}, waitMs);
|
|
133
137
|
};
|
|
@@ -148,6 +152,7 @@ function throttle(fn, intervalMs) {
|
|
|
148
152
|
|
|
149
153
|
if (currentTime - lastCall >= intervalMs) {
|
|
150
154
|
lastCall = currentTime;
|
|
155
|
+
// @ts-ignore
|
|
151
156
|
return fn.apply(this, args);
|
|
152
157
|
}
|
|
153
158
|
};
|
package/src/utils/validation.js
CHANGED
|
@@ -74,7 +74,7 @@ function validatePositiveInt(value, name) {
|
|
|
74
74
|
* Validates that a value is one of the allowed values
|
|
75
75
|
* @param {*} value - Value to validate
|
|
76
76
|
* @param {string} name - Name of the parameter for error messages
|
|
77
|
-
* @param {
|
|
77
|
+
* @param {any[]} allowed - Array of allowed values
|
|
78
78
|
* @throws {ValidationError} If value is not in allowed list
|
|
79
79
|
*/
|
|
80
80
|
function validateEnum(value, name, allowed) {
|