react-native-ble-mesh 1.1.1 → 2.1.0
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 +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +183 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +667 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +90 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +126 -0
- package/src/crypto/providers/QuickCryptoProvider.js +158 -0
- package/src/crypto/providers/TweetNaClProvider.js +124 -0
- package/src/crypto/providers/index.js +11 -0
- package/src/errors/MeshError.js +2 -1
- package/src/expo/withBLEMesh.js +102 -0
- package/src/hooks/AppStateManager.js +9 -1
- package/src/hooks/useMesh.js +47 -13
- package/src/hooks/useMessages.js +6 -4
- package/src/hooks/usePeers.js +13 -9
- package/src/index.js +23 -8
- package/src/mesh/dedup/BloomFilter.js +44 -57
- package/src/mesh/dedup/DedupManager.js +67 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/fragment/Fragmenter.js +1 -1
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +433 -0
- package/src/mesh/monitor/NetworkMonitor.js +376 -320
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/Peer.js +5 -2
- package/src/mesh/peer/PeerManager.js +21 -4
- package/src/mesh/router/MessageRouter.js +38 -19
- package/src/mesh/router/RouteTable.js +24 -8
- package/src/mesh/store/StoreAndForwardManager.js +305 -296
- package/src/mesh/store/index.js +1 -1
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +15 -3
- package/src/protocol/serializer.js +7 -10
- package/src/protocol/validator.js +23 -5
- package/src/service/BatteryOptimizer.js +285 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +163 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +79 -2
- package/src/service/audio/AudioManager.js +8 -2
- package/src/service/file/FileAssembler.js +106 -0
- package/src/service/file/FileChunker.js +79 -0
- package/src/service/file/FileManager.js +307 -0
- package/src/service/file/FileMessage.js +122 -0
- package/src/service/file/index.js +15 -0
- package/src/service/text/TextManager.js +21 -15
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/storage/MessageStore.js +55 -2
- package/src/transport/BLETransport.js +141 -10
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +330 -0
- package/src/transport/WiFiDirectTransport.js +296 -0
- package/src/transport/adapters/NodeBLEAdapter.js +34 -0
- package/src/transport/adapters/RNBLEAdapter.js +56 -1
- package/src/transport/index.js +6 -0
- package/src/utils/EventEmitter.js +6 -9
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +293 -291
- package/src/utils/encoding.js +33 -8
- package/src/crypto/aead.js +0 -189
- package/src/crypto/chacha20.js +0 -181
- package/src/crypto/hkdf.js +0 -187
- package/src/crypto/hmac.js +0 -143
- package/src/crypto/keys/KeyManager.js +0 -271
- package/src/crypto/keys/KeyPair.js +0 -216
- package/src/crypto/keys/SecureStorage.js +0 -219
- package/src/crypto/keys/index.js +0 -32
- package/src/crypto/noise/handshake.js +0 -410
- package/src/crypto/noise/index.js +0 -27
- package/src/crypto/noise/session.js +0 -253
- package/src/crypto/noise/state.js +0 -268
- package/src/crypto/poly1305.js +0 -113
- package/src/crypto/sha256.js +0 -240
- package/src/crypto/x25519.js +0 -154
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Wi-Fi Direct transport for high-bandwidth mesh communication
|
|
5
|
+
* @module transport/WiFiDirectTransport
|
|
6
|
+
*
|
|
7
|
+
* Provides ~250Mbps throughput and ~200m range via Wi-Fi Direct (P2P).
|
|
8
|
+
* Requires: react-native-wifi-p2p (optional peer dependency)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const Transport = require('./Transport');
|
|
12
|
+
const { ConnectionError } = require('../errors');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wi-Fi Direct transport states
|
|
16
|
+
* @constant {Object}
|
|
17
|
+
*/
|
|
18
|
+
const WIFI_DIRECT_STATE = Object.freeze({
|
|
19
|
+
AVAILABLE: 'available',
|
|
20
|
+
UNAVAILABLE: 'unavailable',
|
|
21
|
+
DISCOVERING: 'discovering',
|
|
22
|
+
CONNECTED: 'connected'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wi-Fi Direct transport implementation.
|
|
27
|
+
* Uses react-native-wifi-p2p for peer discovery and data transfer.
|
|
28
|
+
*
|
|
29
|
+
* @class WiFiDirectTransport
|
|
30
|
+
* @extends Transport
|
|
31
|
+
*/
|
|
32
|
+
class WiFiDirectTransport extends Transport {
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} [options={}]
|
|
35
|
+
* @param {Object} [options.wifiP2p] - Injected react-native-wifi-p2p module
|
|
36
|
+
* @param {number} [options.port=8988] - Server port for socket communication
|
|
37
|
+
* @param {number} [options.connectTimeoutMs=15000] - Connection timeout
|
|
38
|
+
* @param {number} [options.maxPeers=4] - Max simultaneous Wi-Fi Direct peers
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
super({ maxPeers: options.maxPeers || 4, ...options });
|
|
42
|
+
|
|
43
|
+
this._wifiP2p = options.wifiP2p || null;
|
|
44
|
+
this._port = options.port || 8988;
|
|
45
|
+
this._connectTimeoutMs = options.connectTimeoutMs || 15000;
|
|
46
|
+
this._isDiscovering = false;
|
|
47
|
+
this._isGroupOwner = false;
|
|
48
|
+
this._groupInfo = null;
|
|
49
|
+
this._subscriptions = [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether discovery is active
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
get isDiscovering() {
|
|
57
|
+
return this._isDiscovering;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether this device is the Wi-Fi Direct group owner
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
get isGroupOwner() {
|
|
65
|
+
return this._isGroupOwner;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Starts the Wi-Fi Direct transport
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async start() {
|
|
73
|
+
if (this.isRunning) { return; }
|
|
74
|
+
this._setState(Transport.STATE.STARTING);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const p2p = this._getWifiP2p();
|
|
78
|
+
await p2p.initialize();
|
|
79
|
+
|
|
80
|
+
// Check if Wi-Fi Direct is supported
|
|
81
|
+
const isAvailable = await p2p.isSuccessfulInitialize();
|
|
82
|
+
if (!isAvailable) {
|
|
83
|
+
throw new ConnectionError('Wi-Fi Direct is not available on this device', 'E100');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._setState(Transport.STATE.RUNNING);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this._setState(Transport.STATE.ERROR);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Stops the Wi-Fi Direct transport
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
97
|
+
async stop() {
|
|
98
|
+
if (this._state === Transport.STATE.STOPPED) { return; }
|
|
99
|
+
this._setState(Transport.STATE.STOPPING);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
if (this._isDiscovering) {
|
|
103
|
+
await this.stopDiscovery();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const p2p = this._getWifiP2p();
|
|
107
|
+
|
|
108
|
+
// Disconnect from group
|
|
109
|
+
try {
|
|
110
|
+
await p2p.removeGroup();
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// Ignore — may not be in a group
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Cleanup subscriptions
|
|
116
|
+
this._subscriptions.forEach(sub => {
|
|
117
|
+
if (sub && typeof sub.remove === 'function') { sub.remove(); }
|
|
118
|
+
});
|
|
119
|
+
this._subscriptions = [];
|
|
120
|
+
|
|
121
|
+
this._peers.clear();
|
|
122
|
+
} finally {
|
|
123
|
+
this._setState(Transport.STATE.STOPPED);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Starts discovering nearby Wi-Fi Direct devices
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
async startDiscovery() {
|
|
132
|
+
if (!this.isRunning || this._isDiscovering) { return; }
|
|
133
|
+
|
|
134
|
+
const p2p = this._getWifiP2p();
|
|
135
|
+
await p2p.discoverPeers();
|
|
136
|
+
this._isDiscovering = true;
|
|
137
|
+
this.emit('discoveryStarted');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Stops device discovery
|
|
142
|
+
* @returns {Promise<void>}
|
|
143
|
+
*/
|
|
144
|
+
async stopDiscovery() {
|
|
145
|
+
if (!this._isDiscovering) { return; }
|
|
146
|
+
|
|
147
|
+
const p2p = this._getWifiP2p();
|
|
148
|
+
try {
|
|
149
|
+
await p2p.stopDiscoveringPeers();
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// Ignore
|
|
152
|
+
}
|
|
153
|
+
this._isDiscovering = false;
|
|
154
|
+
this.emit('discoveryStopped');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Connects to a Wi-Fi Direct peer
|
|
159
|
+
* @param {string} peerId - Device address
|
|
160
|
+
* @returns {Promise<void>}
|
|
161
|
+
*/
|
|
162
|
+
async connectToPeer(peerId) {
|
|
163
|
+
if (!this.isRunning) {
|
|
164
|
+
throw new Error('Transport is not running');
|
|
165
|
+
}
|
|
166
|
+
if (this._peers.has(peerId)) {
|
|
167
|
+
throw ConnectionError.fromCode('E206', peerId);
|
|
168
|
+
}
|
|
169
|
+
if (!this.canAcceptPeer()) {
|
|
170
|
+
throw ConnectionError.fromCode('E203', peerId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const p2p = this._getWifiP2p();
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await p2p.connect(peerId);
|
|
177
|
+
|
|
178
|
+
const connectionInfo = await p2p.getConnectionInfo();
|
|
179
|
+
this._isGroupOwner = connectionInfo.isGroupOwner || false;
|
|
180
|
+
this._groupInfo = connectionInfo;
|
|
181
|
+
|
|
182
|
+
this._peers.set(peerId, {
|
|
183
|
+
peerId,
|
|
184
|
+
connectedAt: Date.now(),
|
|
185
|
+
isGroupOwner: this._isGroupOwner,
|
|
186
|
+
groupOwnerAddress: connectionInfo.groupOwnerAddress
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
this.emit('peerConnected', { peerId, transport: 'wifi-direct' });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw ConnectionError.connectionFailed(peerId, { cause: error.message });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Disconnects from a peer
|
|
197
|
+
* @param {string} peerId
|
|
198
|
+
* @returns {Promise<void>}
|
|
199
|
+
*/
|
|
200
|
+
async disconnectFromPeer(peerId) {
|
|
201
|
+
if (!this._peers.has(peerId)) { return; }
|
|
202
|
+
|
|
203
|
+
const p2p = this._getWifiP2p();
|
|
204
|
+
try {
|
|
205
|
+
await p2p.removeGroup();
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// Ignore
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this._peers.delete(peerId);
|
|
211
|
+
this.emit('peerDisconnected', { peerId, reason: 'user_request' });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Sends data to a peer via Wi-Fi Direct socket
|
|
216
|
+
* @param {string} peerId - Target peer
|
|
217
|
+
* @param {Uint8Array} data - Data to send
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
220
|
+
async send(peerId, data) {
|
|
221
|
+
if (!this.isRunning) { throw new Error('Transport is not running'); }
|
|
222
|
+
if (!this._peers.has(peerId)) {
|
|
223
|
+
throw ConnectionError.fromCode('E207', peerId);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const p2p = this._getWifiP2p();
|
|
227
|
+
const peerInfo = this._peers.get(peerId);
|
|
228
|
+
|
|
229
|
+
// Convert Uint8Array to base64 for transfer
|
|
230
|
+
const base64 = this._uint8ArrayToBase64(data);
|
|
231
|
+
|
|
232
|
+
if (this._isGroupOwner) {
|
|
233
|
+
// Group owner sends via server socket
|
|
234
|
+
await p2p.sendMessage(base64);
|
|
235
|
+
} else {
|
|
236
|
+
// Client sends to group owner address
|
|
237
|
+
await p2p.sendMessageTo(peerInfo.groupOwnerAddress, this._port, base64);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Broadcasts to all connected peers
|
|
243
|
+
* @param {Uint8Array} data
|
|
244
|
+
* @returns {Promise<string[]>}
|
|
245
|
+
*/
|
|
246
|
+
async broadcast(data) {
|
|
247
|
+
if (!this.isRunning) { throw new Error('Transport is not running'); }
|
|
248
|
+
|
|
249
|
+
const peerIds = this.getConnectedPeers();
|
|
250
|
+
const results = await Promise.allSettled(
|
|
251
|
+
peerIds.map(id => this.send(id, data))
|
|
252
|
+
);
|
|
253
|
+
return peerIds.filter((_, i) => results[i].status === 'fulfilled');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Gets available Wi-Fi Direct peers (discovered but not connected)
|
|
258
|
+
* @returns {Promise<Object[]>}
|
|
259
|
+
*/
|
|
260
|
+
async getAvailablePeers() {
|
|
261
|
+
if (!this.isRunning) { return []; }
|
|
262
|
+
const p2p = this._getWifiP2p();
|
|
263
|
+
try {
|
|
264
|
+
return await p2p.getAvailablePeers();
|
|
265
|
+
} catch (e) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** @private */
|
|
271
|
+
_getWifiP2p() {
|
|
272
|
+
if (!this._wifiP2p) {
|
|
273
|
+
try {
|
|
274
|
+
this._wifiP2p = require('react-native-wifi-p2p');
|
|
275
|
+
} catch (e) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
'react-native-wifi-p2p is required for WiFiDirectTransport. ' +
|
|
278
|
+
'Install: npm install react-native-wifi-p2p'
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return this._wifiP2p;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** @private */
|
|
286
|
+
_uint8ArrayToBase64(bytes) {
|
|
287
|
+
const chunks = [];
|
|
288
|
+
for (let i = 0; i < bytes.length; i += 8192) {
|
|
289
|
+
chunks.push(String.fromCharCode.apply(null, bytes.subarray(i, Math.min(i + 8192, bytes.length))));
|
|
290
|
+
}
|
|
291
|
+
const binary = chunks.join('');
|
|
292
|
+
return typeof btoa !== 'undefined' ? btoa(binary) : Buffer.from(bytes).toString('base64');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = { WiFiDirectTransport, WIFI_DIRECT_STATE };
|
|
@@ -60,6 +60,13 @@ class NodeBLEAdapter extends BLEAdapter {
|
|
|
60
60
|
* @private
|
|
61
61
|
*/
|
|
62
62
|
this._scanCallback = null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Disconnect callback
|
|
66
|
+
* @type {Function|null}
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
this._disconnectCallback = null;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
@@ -181,6 +188,18 @@ class NodeBLEAdapter extends BLEAdapter {
|
|
|
181
188
|
// Monitor disconnection
|
|
182
189
|
peripheral.once('disconnect', () => {
|
|
183
190
|
this._peripherals.delete(deviceId);
|
|
191
|
+
|
|
192
|
+
// Clean up subscriptions for this device
|
|
193
|
+
for (const [key] of this._subscriptions.entries()) {
|
|
194
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
195
|
+
this._subscriptions.delete(key);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Notify transport
|
|
200
|
+
if (this._disconnectCallback) {
|
|
201
|
+
this._disconnectCallback(deviceId);
|
|
202
|
+
}
|
|
184
203
|
});
|
|
185
204
|
|
|
186
205
|
return {
|
|
@@ -196,6 +215,13 @@ class NodeBLEAdapter extends BLEAdapter {
|
|
|
196
215
|
* @returns {Promise<void>}
|
|
197
216
|
*/
|
|
198
217
|
async disconnect(deviceId) {
|
|
218
|
+
// Clean up subscriptions first
|
|
219
|
+
for (const [key] of this._subscriptions.entries()) {
|
|
220
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
221
|
+
this._subscriptions.delete(key);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
199
225
|
const peripheral = this._peripherals.get(deviceId);
|
|
200
226
|
if (peripheral) {
|
|
201
227
|
await this._disconnectPeripheral(peripheral);
|
|
@@ -410,6 +436,14 @@ class NodeBLEAdapter extends BLEAdapter {
|
|
|
410
436
|
});
|
|
411
437
|
}
|
|
412
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Registers a callback for device disconnection events
|
|
441
|
+
* @param {Function} callback - Callback function receiving peerId
|
|
442
|
+
*/
|
|
443
|
+
onDeviceDisconnected(callback) {
|
|
444
|
+
this._disconnectCallback = callback;
|
|
445
|
+
}
|
|
446
|
+
|
|
413
447
|
/**
|
|
414
448
|
* Ensures the adapter is initialized
|
|
415
449
|
* @throws {Error} If not initialized
|
|
@@ -40,6 +40,13 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
40
40
|
*/
|
|
41
41
|
this._BleManager = options.BleManager || null;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* iOS state restoration identifier
|
|
45
|
+
* @type {string|null}
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
this._restoreIdentifier = options.restoreIdentifier || null;
|
|
49
|
+
|
|
43
50
|
/**
|
|
44
51
|
* Connected devices map
|
|
45
52
|
* @type {Map<string, Object>}
|
|
@@ -67,6 +74,13 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
67
74
|
* @private
|
|
68
75
|
*/
|
|
69
76
|
this._stateSubscription = null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Disconnect callback
|
|
80
|
+
* @type {Function|null}
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
this._disconnectCallback = null;
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
/**
|
|
@@ -91,7 +105,19 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
const managerOptions = {};
|
|
109
|
+
if (this._restoreIdentifier) {
|
|
110
|
+
managerOptions.restoreStateIdentifier = this._restoreIdentifier;
|
|
111
|
+
managerOptions.restoreStateFunction = (restoredState) => {
|
|
112
|
+
// Re-populate devices from restored state
|
|
113
|
+
if (restoredState && restoredState.connectedPeripherals) {
|
|
114
|
+
for (const peripheral of restoredState.connectedPeripherals) {
|
|
115
|
+
this._devices.set(peripheral.id, peripheral);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
this._manager = new this._BleManager(managerOptions);
|
|
95
121
|
|
|
96
122
|
// Subscribe to state changes
|
|
97
123
|
this._stateSubscription = this._manager.onStateChange((state) => {
|
|
@@ -187,6 +213,19 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
187
213
|
// Monitor disconnection
|
|
188
214
|
device.onDisconnected(() => {
|
|
189
215
|
this._devices.delete(deviceId);
|
|
216
|
+
|
|
217
|
+
// Clean up subscriptions for this device
|
|
218
|
+
for (const [key, subscription] of this._subscriptions.entries()) {
|
|
219
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
220
|
+
subscription.remove();
|
|
221
|
+
this._subscriptions.delete(key);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Notify transport
|
|
226
|
+
if (this._disconnectCallback) {
|
|
227
|
+
this._disconnectCallback(deviceId);
|
|
228
|
+
}
|
|
190
229
|
});
|
|
191
230
|
|
|
192
231
|
return {
|
|
@@ -202,6 +241,14 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
202
241
|
* @returns {Promise<void>}
|
|
203
242
|
*/
|
|
204
243
|
async disconnect(deviceId) {
|
|
244
|
+
// Clean up subscriptions first
|
|
245
|
+
for (const [key, subscription] of this._subscriptions.entries()) {
|
|
246
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
247
|
+
subscription.remove();
|
|
248
|
+
this._subscriptions.delete(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
205
252
|
const device = this._devices.get(deviceId);
|
|
206
253
|
if (device) {
|
|
207
254
|
await this._manager.cancelDeviceConnection(deviceId);
|
|
@@ -284,6 +331,14 @@ class RNBLEAdapter extends BLEAdapter {
|
|
|
284
331
|
return stateMap[state] || BLEAdapter.STATE.UNKNOWN;
|
|
285
332
|
}
|
|
286
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Registers a callback for device disconnection events
|
|
336
|
+
* @param {Function} callback - Callback function receiving peerId
|
|
337
|
+
*/
|
|
338
|
+
onDeviceDisconnected(callback) {
|
|
339
|
+
this._disconnectCallback = callback;
|
|
340
|
+
}
|
|
341
|
+
|
|
287
342
|
/**
|
|
288
343
|
* Ensures the adapter is initialized
|
|
289
344
|
* @throws {Error} If not initialized
|
package/src/transport/index.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
const Transport = require('./Transport');
|
|
9
9
|
const MockTransport = require('./MockTransport');
|
|
10
10
|
const BLETransport = require('./BLETransport');
|
|
11
|
+
const { WiFiDirectTransport, WIFI_DIRECT_STATE } = require('./WiFiDirectTransport');
|
|
12
|
+
const { MultiTransport, STRATEGY: MULTI_TRANSPORT_STRATEGY } = require('./MultiTransport');
|
|
11
13
|
|
|
12
14
|
// React Native compatible adapters (from adapters subdirectory)
|
|
13
15
|
const BLEAdapter = require('./adapters/BLEAdapter');
|
|
@@ -17,6 +19,10 @@ module.exports = {
|
|
|
17
19
|
Transport,
|
|
18
20
|
MockTransport,
|
|
19
21
|
BLETransport,
|
|
22
|
+
WiFiDirectTransport,
|
|
23
|
+
WIFI_DIRECT_STATE,
|
|
24
|
+
MultiTransport,
|
|
25
|
+
MULTI_TRANSPORT_STRATEGY,
|
|
20
26
|
BLEAdapter,
|
|
21
27
|
RNBLEAdapter,
|
|
22
28
|
|
|
@@ -116,10 +116,11 @@ class EventEmitter {
|
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
const listeners = this._events.get(event)
|
|
120
|
-
const
|
|
119
|
+
const listeners = this._events.get(event);
|
|
120
|
+
const hasOnce = listeners.some(e => e.once);
|
|
121
|
+
const iterList = hasOnce ? listeners.slice() : listeners;
|
|
121
122
|
|
|
122
|
-
for (const entry of
|
|
123
|
+
for (const entry of iterList) {
|
|
123
124
|
try {
|
|
124
125
|
entry.listener.apply(this, args);
|
|
125
126
|
} catch (error) {
|
|
@@ -130,15 +131,11 @@ class EventEmitter {
|
|
|
130
131
|
console.error('Error in error handler:', error);
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
if (entry.once) {
|
|
135
|
-
toRemove.push(entry);
|
|
136
|
-
}
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
// Remove one-time listeners
|
|
140
|
-
if (
|
|
141
|
-
const remaining =
|
|
137
|
+
if (hasOnce) {
|
|
138
|
+
const remaining = listeners.filter(e => !e.once);
|
|
142
139
|
if (remaining.length === 0) {
|
|
143
140
|
this._events.delete(event);
|
|
144
141
|
} else {
|
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
|
/**
|