@onekeyfe/hardware-cli 1.2.2-alpha.100 → 1.2.2-alpha.12
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.
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import type { LowlevelTransportSharedPlugin } from '@onekeyfe/hd-transport';
|
|
3
|
+
export declare function resolveNobleProtocolV2PacketCapacity(mtu: number | null | undefined, platform?: NodeJS.Platform): number;
|
|
2
4
|
export declare function createNobleBlePlugin(): LowlevelTransportSharedPlugin;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createNobleBlePlugin = void 0;
|
|
3
|
+
exports.createNobleBlePlugin = exports.resolveNobleProtocolV2PacketCapacity = void 0;
|
|
4
4
|
const hd_shared_1 = require("@onekeyfe/hd-shared");
|
|
5
5
|
const ONEKEY_SERVICE_UUIDS = [hd_shared_1.ONEKEY_SERVICE_UUID];
|
|
6
6
|
const ONEKEY_SERVICE_UUID_ALIASES = (0, hd_shared_1.createKnownBleUuidAliases)(hd_shared_1.ONEKEY_SERVICE_UUID);
|
|
@@ -11,8 +11,22 @@ const DEVICE_SCAN_TIMEOUT = 8000;
|
|
|
11
11
|
const CONNECTION_TIMEOUT = 8000;
|
|
12
12
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
13
13
|
const BLE_CLEANUP_TIMEOUT = 100;
|
|
14
|
-
const
|
|
14
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
15
|
+
const BLE_PACKET_SIZE_MAX = 244;
|
|
16
|
+
const ATT_WRITE_HEADER_SIZE = 3;
|
|
15
17
|
const BLE_ENCRYPTION_ERROR_PATTERNS = [/encryption is insufficient/i, /insufficient encryption/i];
|
|
18
|
+
function resolveNobleProtocolV2PacketCapacity(mtu, platform = process.platform) {
|
|
19
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 0) {
|
|
20
|
+
return BLE_PACKET_SIZE_FALLBACK;
|
|
21
|
+
}
|
|
22
|
+
const reportedCapacity = Math.floor(mtu);
|
|
23
|
+
const payloadCapacity = platform === 'linux' ? reportedCapacity - ATT_WRITE_HEADER_SIZE : reportedCapacity;
|
|
24
|
+
if (payloadCapacity <= 0) {
|
|
25
|
+
return BLE_PACKET_SIZE_FALLBACK;
|
|
26
|
+
}
|
|
27
|
+
return Math.min(payloadCapacity, BLE_PACKET_SIZE_MAX);
|
|
28
|
+
}
|
|
29
|
+
exports.resolveNobleProtocolV2PacketCapacity = resolveNobleProtocolV2PacketCapacity;
|
|
16
30
|
let noble = null;
|
|
17
31
|
let nobleReadyPromise = null;
|
|
18
32
|
const discoveredDevices = new Map();
|
|
@@ -20,6 +34,7 @@ const connectedDevices = new Map();
|
|
|
20
34
|
const deviceCharacteristics = new Map();
|
|
21
35
|
const notificationStates = new Map();
|
|
22
36
|
const notificationGenerations = new Map();
|
|
37
|
+
const disconnectListeners = new Map();
|
|
23
38
|
function isOneKeyPeripheral(peripheral) {
|
|
24
39
|
const serviceUuids = peripheral.advertisement?.serviceUuids;
|
|
25
40
|
return ((0, hd_shared_1.hasOnekeyCommunicationService)(serviceUuids) &&
|
|
@@ -63,11 +78,32 @@ function clearNotificationState(deviceId, reason) {
|
|
|
63
78
|
if (!state)
|
|
64
79
|
return;
|
|
65
80
|
notificationStates.delete(deviceId);
|
|
66
|
-
const error = new Error(reason);
|
|
81
|
+
const error = reason instanceof Error ? reason : new Error(reason);
|
|
67
82
|
state.pendingReceivers.forEach(receiver => receiver.reject(error));
|
|
68
83
|
state.pendingReceivers.clear();
|
|
69
84
|
state.queue.length = 0;
|
|
70
85
|
}
|
|
86
|
+
function removeDisconnectListener(deviceId) {
|
|
87
|
+
const tracked = disconnectListeners.get(deviceId);
|
|
88
|
+
if (!tracked)
|
|
89
|
+
return;
|
|
90
|
+
tracked.peripheral.removeListener('disconnect', tracked.listener);
|
|
91
|
+
disconnectListeners.delete(deviceId);
|
|
92
|
+
}
|
|
93
|
+
function trackUnexpectedDisconnect(deviceId, peripheral) {
|
|
94
|
+
removeDisconnectListener(deviceId);
|
|
95
|
+
const listener = (reason) => {
|
|
96
|
+
removeDisconnectListener(deviceId);
|
|
97
|
+
if (connectedDevices.get(deviceId) !== peripheral)
|
|
98
|
+
return;
|
|
99
|
+
deviceCharacteristics.get(deviceId)?.notify.removeAllListeners('data');
|
|
100
|
+
connectedDevices.delete(deviceId);
|
|
101
|
+
deviceCharacteristics.delete(deviceId);
|
|
102
|
+
clearNotificationState(deviceId, hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleDeviceDisconnected, reason || `BLE device disconnected: ${deviceId}`));
|
|
103
|
+
};
|
|
104
|
+
disconnectListeners.set(deviceId, { peripheral, listener });
|
|
105
|
+
peripheral.on('disconnect', listener);
|
|
106
|
+
}
|
|
71
107
|
function waitForNobleCleanup(registerCallback) {
|
|
72
108
|
return new Promise(resolve => {
|
|
73
109
|
let completed = false;
|
|
@@ -280,6 +316,7 @@ function writeCharacteristic(characteristic, buffer, withoutResponse) {
|
|
|
280
316
|
async function disconnectDevice(uuid) {
|
|
281
317
|
const peripheral = connectedDevices.get(uuid);
|
|
282
318
|
const characteristics = deviceCharacteristics.get(uuid);
|
|
319
|
+
removeDisconnectListener(uuid);
|
|
283
320
|
clearNotificationState(uuid, `BLE device disconnected: ${uuid}`);
|
|
284
321
|
if (characteristics) {
|
|
285
322
|
characteristics.notify.removeAllListeners('data');
|
|
@@ -321,8 +358,10 @@ function createNobleBlePlugin() {
|
|
|
321
358
|
await subscribeNotifications(uuid, notificationState.generation, characteristics.notify);
|
|
322
359
|
connectedDevices.set(uuid, peripheral);
|
|
323
360
|
deviceCharacteristics.set(uuid, characteristics);
|
|
361
|
+
trackUnexpectedDisconnect(uuid, peripheral);
|
|
324
362
|
}
|
|
325
363
|
catch (error) {
|
|
364
|
+
removeDisconnectListener(uuid);
|
|
326
365
|
clearNotificationState(uuid, `BLE notification subscription failed: ${uuid}`);
|
|
327
366
|
if (characteristics) {
|
|
328
367
|
characteristics.notify.removeAllListeners('data');
|
|
@@ -337,6 +376,9 @@ function createNobleBlePlugin() {
|
|
|
337
376
|
async disconnect(uuid) {
|
|
338
377
|
await disconnectDevice(uuid);
|
|
339
378
|
},
|
|
379
|
+
getProtocolV2PacketCapacity(uuid) {
|
|
380
|
+
return resolveNobleProtocolV2PacketCapacity(connectedDevices.get(uuid)?.mtu);
|
|
381
|
+
},
|
|
340
382
|
async send(uuid, data, options) {
|
|
341
383
|
const characteristics = deviceCharacteristics.get(uuid);
|
|
342
384
|
if (!characteristics) {
|
|
@@ -344,8 +386,9 @@ function createNobleBlePlugin() {
|
|
|
344
386
|
}
|
|
345
387
|
const buffer = Buffer.from(data, 'hex');
|
|
346
388
|
const withoutResponse = options?.withoutResponse ?? true;
|
|
347
|
-
|
|
348
|
-
|
|
389
|
+
const packetCapacity = resolveNobleProtocolV2PacketCapacity(connectedDevices.get(uuid)?.mtu);
|
|
390
|
+
for (let offset = 0; offset < buffer.length; offset += packetCapacity) {
|
|
391
|
+
const chunk = buffer.subarray(offset, Math.min(offset + packetCapacity, buffer.length));
|
|
349
392
|
await writeCharacteristic(characteristics.write, chunk, withoutResponse);
|
|
350
393
|
}
|
|
351
394
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hardware-cli",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.12",
|
|
4
4
|
"description": "OneKey hardware wallet CLI for testing device communication",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"test": "jest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@onekeyfe/hd-common-connect-sdk": "1.2.2-alpha.
|
|
35
|
-
"@onekeyfe/hd-core": "1.2.2-alpha.
|
|
36
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
37
|
-
"@onekeyfe/hd-transport-usb": "1.2.2-alpha.
|
|
34
|
+
"@onekeyfe/hd-common-connect-sdk": "1.2.2-alpha.12",
|
|
35
|
+
"@onekeyfe/hd-core": "1.2.2-alpha.12",
|
|
36
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.12",
|
|
37
|
+
"@onekeyfe/hd-transport-usb": "1.2.2-alpha.12",
|
|
38
38
|
"@stoprocent/noble": "2.3.16",
|
|
39
39
|
"commander": "^12.0.0"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "9b5a45bc55819d22ed752899b4c40aad70de4527"
|
|
42
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
3
|
|
|
3
4
|
type MockCharacteristic = EventEmitter & {
|
|
4
5
|
uuid: string;
|
|
@@ -27,18 +28,20 @@ const createPeripheral = (id: string) => {
|
|
|
27
28
|
uuid: '0001',
|
|
28
29
|
discoverCharacteristics: jest.fn((_uuids, callback) => callback(null, [write, notify])),
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
discoverServices: jest.fn((_uuids, callback) => callback(null, [service])),
|
|
39
|
-
connect: jest.fn(callback => callback()),
|
|
40
|
-
disconnect: jest.fn(callback => callback()),
|
|
31
|
+
const peripheral = Object.assign(new EventEmitter(), {
|
|
32
|
+
id,
|
|
33
|
+
state: 'connected',
|
|
34
|
+
mtu: null as number | null,
|
|
35
|
+
advertisement: {
|
|
36
|
+
localName: `OneKey Pro 2 ${id}`,
|
|
37
|
+
serviceUuids: ['0001'],
|
|
41
38
|
},
|
|
39
|
+
discoverServices: jest.fn((_uuids, callback) => callback(null, [service])),
|
|
40
|
+
connect: jest.fn(callback => callback()),
|
|
41
|
+
disconnect: jest.fn(callback => callback()),
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
peripheral,
|
|
42
45
|
service,
|
|
43
46
|
write,
|
|
44
47
|
notify,
|
|
@@ -52,6 +55,15 @@ describe('Noble BLE plugin notification routing', () => {
|
|
|
52
55
|
jest.clearAllMocks();
|
|
53
56
|
});
|
|
54
57
|
|
|
58
|
+
test('normalizes the platform-specific Noble MTU to a safe write capacity', async () => {
|
|
59
|
+
const { resolveNobleProtocolV2PacketCapacity } = await import('../transports/nobleBlePlugin');
|
|
60
|
+
|
|
61
|
+
expect(resolveNobleProtocolV2PacketCapacity(null, 'darwin')).toBe(192);
|
|
62
|
+
expect(resolveNobleProtocolV2PacketCapacity(244, 'darwin')).toBe(244);
|
|
63
|
+
expect(resolveNobleProtocolV2PacketCapacity(247, 'linux')).toBe(244);
|
|
64
|
+
expect(resolveNobleProtocolV2PacketCapacity(512, 'win32')).toBe(244);
|
|
65
|
+
});
|
|
66
|
+
|
|
55
67
|
test('does not enumerate Find My advertisements that expose FFFD', async () => {
|
|
56
68
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
57
69
|
const oneKey = createPeripheral('onekey-device');
|
|
@@ -177,6 +189,38 @@ describe('Noble BLE plugin notification routing', () => {
|
|
|
177
189
|
expect(result).toBe('completed');
|
|
178
190
|
});
|
|
179
191
|
|
|
192
|
+
test('rejects a pending receive when the peripheral disconnects unexpectedly', async () => {
|
|
193
|
+
const device = createPeripheral('device-a');
|
|
194
|
+
const noble = new EventEmitter() as EventEmitter & {
|
|
195
|
+
state: string;
|
|
196
|
+
startScanning: jest.Mock;
|
|
197
|
+
stopScanning: jest.Mock;
|
|
198
|
+
};
|
|
199
|
+
noble.state = 'poweredOn';
|
|
200
|
+
noble.startScanning = jest.fn((_services, _duplicates, callback) => {
|
|
201
|
+
callback?.();
|
|
202
|
+
noble.emit('discover', device.peripheral);
|
|
203
|
+
});
|
|
204
|
+
noble.stopScanning = jest.fn(callback => callback?.());
|
|
205
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
206
|
+
|
|
207
|
+
const { createNobleBlePlugin } = await import('../transports/nobleBlePlugin');
|
|
208
|
+
const plugin = createNobleBlePlugin();
|
|
209
|
+
await plugin.init();
|
|
210
|
+
await plugin.connect('device-a');
|
|
211
|
+
|
|
212
|
+
const receive = plugin.receive('device-a');
|
|
213
|
+
device.peripheral.state = 'disconnected';
|
|
214
|
+
device.peripheral.emit('disconnect', 'Remote User Terminated Connection');
|
|
215
|
+
|
|
216
|
+
await expect(receive).rejects.toMatchObject({
|
|
217
|
+
errorCode: HardwareErrorCode.BleDeviceDisconnected,
|
|
218
|
+
});
|
|
219
|
+
await expect(plugin.receive('device-a')).rejects.toMatchObject({
|
|
220
|
+
errorCode: HardwareErrorCode.TransportNotFound,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
180
224
|
test('disconnects an untracked peripheral when service discovery fails', async () => {
|
|
181
225
|
const device = createPeripheral('device-a');
|
|
182
226
|
device.peripheral.discoverServices.mockImplementation((_uuids, callback) =>
|
|
@@ -342,6 +386,33 @@ describe('Noble BLE plugin notification routing', () => {
|
|
|
342
386
|
expect(wait).not.toHaveBeenCalled();
|
|
343
387
|
});
|
|
344
388
|
|
|
389
|
+
test('uses the connected peripheral write capacity without padding', async () => {
|
|
390
|
+
const device = createPeripheral('device-a');
|
|
391
|
+
device.peripheral.mtu = process.platform === 'linux' ? 247 : 244;
|
|
392
|
+
const noble = new EventEmitter() as EventEmitter & {
|
|
393
|
+
state: string;
|
|
394
|
+
startScanning: jest.Mock;
|
|
395
|
+
stopScanning: jest.Mock;
|
|
396
|
+
};
|
|
397
|
+
noble.state = 'poweredOn';
|
|
398
|
+
noble.startScanning = jest.fn((_services, _duplicates, callback) => {
|
|
399
|
+
callback?.();
|
|
400
|
+
noble.emit('discover', device.peripheral);
|
|
401
|
+
});
|
|
402
|
+
noble.stopScanning = jest.fn(callback => callback?.());
|
|
403
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
404
|
+
|
|
405
|
+
const { createNobleBlePlugin } = await import('../transports/nobleBlePlugin');
|
|
406
|
+
const plugin = createNobleBlePlugin();
|
|
407
|
+
await plugin.init();
|
|
408
|
+
await plugin.connect('device-a');
|
|
409
|
+
|
|
410
|
+
await plugin.send('device-a', 'aa'.repeat(245));
|
|
411
|
+
|
|
412
|
+
expect(plugin.getProtocolV2PacketCapacity?.('device-a')).toBe(244);
|
|
413
|
+
expect(device.write.write.mock.calls.map(([packet]) => packet.length)).toEqual([244, 1]);
|
|
414
|
+
});
|
|
415
|
+
|
|
345
416
|
test('preserves a short final BLE packet without padding', async () => {
|
|
346
417
|
const device = createPeripheral('device-a');
|
|
347
418
|
const noble = new EventEmitter() as EventEmitter & {
|
|
@@ -43,6 +43,11 @@ type NobleNotificationState = {
|
|
|
43
43
|
pendingReceivers: Set<NoblePendingReceiver>;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
type NobleDisconnectListener = {
|
|
47
|
+
peripheral: Peripheral;
|
|
48
|
+
listener: (reason: string) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
46
51
|
const ONEKEY_SERVICE_UUIDS = [ONEKEY_SERVICE_UUID];
|
|
47
52
|
const ONEKEY_SERVICE_UUID_ALIASES = createKnownBleUuidAliases(ONEKEY_SERVICE_UUID);
|
|
48
53
|
const ONEKEY_WRITE_UUID_ALIASES = createKnownBleUuidAliases(ONEKEY_WRITE_CHARACTERISTIC_UUID);
|
|
@@ -53,9 +58,27 @@ const DEVICE_SCAN_TIMEOUT = 8_000;
|
|
|
53
58
|
const CONNECTION_TIMEOUT = 8_000;
|
|
54
59
|
const SERVICE_DISCOVERY_TIMEOUT = 10_000;
|
|
55
60
|
const BLE_CLEANUP_TIMEOUT = 100;
|
|
56
|
-
const
|
|
61
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
62
|
+
const BLE_PACKET_SIZE_MAX = 244;
|
|
63
|
+
const ATT_WRITE_HEADER_SIZE = 3;
|
|
57
64
|
const BLE_ENCRYPTION_ERROR_PATTERNS = [/encryption is insufficient/i, /insufficient encryption/i];
|
|
58
65
|
|
|
66
|
+
export function resolveNobleProtocolV2PacketCapacity(
|
|
67
|
+
mtu: number | null | undefined,
|
|
68
|
+
platform: NodeJS.Platform = process.platform
|
|
69
|
+
) {
|
|
70
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 0) {
|
|
71
|
+
return BLE_PACKET_SIZE_FALLBACK;
|
|
72
|
+
}
|
|
73
|
+
const reportedCapacity = Math.floor(mtu);
|
|
74
|
+
const payloadCapacity =
|
|
75
|
+
platform === 'linux' ? reportedCapacity - ATT_WRITE_HEADER_SIZE : reportedCapacity;
|
|
76
|
+
if (payloadCapacity <= 0) {
|
|
77
|
+
return BLE_PACKET_SIZE_FALLBACK;
|
|
78
|
+
}
|
|
79
|
+
return Math.min(payloadCapacity, BLE_PACKET_SIZE_MAX);
|
|
80
|
+
}
|
|
81
|
+
|
|
59
82
|
let noble: NobleModule | null = null;
|
|
60
83
|
let nobleReadyPromise: Promise<void> | null = null;
|
|
61
84
|
const discoveredDevices = new Map<string, Peripheral>();
|
|
@@ -63,6 +86,7 @@ const connectedDevices = new Map<string, Peripheral>();
|
|
|
63
86
|
const deviceCharacteristics = new Map<string, CharacteristicPair>();
|
|
64
87
|
const notificationStates = new Map<string, NobleNotificationState>();
|
|
65
88
|
const notificationGenerations = new Map<string, number>();
|
|
89
|
+
const disconnectListeners = new Map<string, NobleDisconnectListener>();
|
|
66
90
|
|
|
67
91
|
function isOneKeyPeripheral(peripheral: Peripheral) {
|
|
68
92
|
const serviceUuids = peripheral.advertisement?.serviceUuids;
|
|
@@ -108,17 +132,45 @@ function createNotificationState(deviceId: string) {
|
|
|
108
132
|
return state;
|
|
109
133
|
}
|
|
110
134
|
|
|
111
|
-
function clearNotificationState(deviceId: string, reason: string) {
|
|
135
|
+
function clearNotificationState(deviceId: string, reason: string | Error) {
|
|
112
136
|
const state = notificationStates.get(deviceId);
|
|
113
137
|
if (!state) return;
|
|
114
138
|
|
|
115
139
|
notificationStates.delete(deviceId);
|
|
116
|
-
const error = new Error(reason);
|
|
140
|
+
const error = reason instanceof Error ? reason : new Error(reason);
|
|
117
141
|
state.pendingReceivers.forEach(receiver => receiver.reject(error));
|
|
118
142
|
state.pendingReceivers.clear();
|
|
119
143
|
state.queue.length = 0;
|
|
120
144
|
}
|
|
121
145
|
|
|
146
|
+
function removeDisconnectListener(deviceId: string) {
|
|
147
|
+
const tracked = disconnectListeners.get(deviceId);
|
|
148
|
+
if (!tracked) return;
|
|
149
|
+
tracked.peripheral.removeListener('disconnect', tracked.listener);
|
|
150
|
+
disconnectListeners.delete(deviceId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function trackUnexpectedDisconnect(deviceId: string, peripheral: Peripheral) {
|
|
154
|
+
removeDisconnectListener(deviceId);
|
|
155
|
+
const listener = (reason: string) => {
|
|
156
|
+
removeDisconnectListener(deviceId);
|
|
157
|
+
if (connectedDevices.get(deviceId) !== peripheral) return;
|
|
158
|
+
|
|
159
|
+
deviceCharacteristics.get(deviceId)?.notify.removeAllListeners('data');
|
|
160
|
+
connectedDevices.delete(deviceId);
|
|
161
|
+
deviceCharacteristics.delete(deviceId);
|
|
162
|
+
clearNotificationState(
|
|
163
|
+
deviceId,
|
|
164
|
+
ERRORS.TypedError(
|
|
165
|
+
HardwareErrorCode.BleDeviceDisconnected,
|
|
166
|
+
reason || `BLE device disconnected: ${deviceId}`
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
disconnectListeners.set(deviceId, { peripheral, listener });
|
|
171
|
+
peripheral.on('disconnect', listener);
|
|
172
|
+
}
|
|
173
|
+
|
|
122
174
|
function waitForNobleCleanup(registerCallback: (callback: () => void) => void) {
|
|
123
175
|
return new Promise<void>(resolve => {
|
|
124
176
|
let completed = false;
|
|
@@ -377,6 +429,7 @@ function writeCharacteristic(
|
|
|
377
429
|
async function disconnectDevice(uuid: string) {
|
|
378
430
|
const peripheral = connectedDevices.get(uuid);
|
|
379
431
|
const characteristics = deviceCharacteristics.get(uuid);
|
|
432
|
+
removeDisconnectListener(uuid);
|
|
380
433
|
clearNotificationState(uuid, `BLE device disconnected: ${uuid}`);
|
|
381
434
|
if (characteristics) {
|
|
382
435
|
characteristics.notify.removeAllListeners('data');
|
|
@@ -425,7 +478,9 @@ export function createNobleBlePlugin(): LowlevelTransportSharedPlugin {
|
|
|
425
478
|
await subscribeNotifications(uuid, notificationState.generation, characteristics.notify);
|
|
426
479
|
connectedDevices.set(uuid, peripheral);
|
|
427
480
|
deviceCharacteristics.set(uuid, characteristics);
|
|
481
|
+
trackUnexpectedDisconnect(uuid, peripheral);
|
|
428
482
|
} catch (error) {
|
|
483
|
+
removeDisconnectListener(uuid);
|
|
429
484
|
clearNotificationState(uuid, `BLE notification subscription failed: ${uuid}`);
|
|
430
485
|
if (characteristics) {
|
|
431
486
|
characteristics.notify.removeAllListeners('data');
|
|
@@ -442,6 +497,10 @@ export function createNobleBlePlugin(): LowlevelTransportSharedPlugin {
|
|
|
442
497
|
await disconnectDevice(uuid);
|
|
443
498
|
},
|
|
444
499
|
|
|
500
|
+
getProtocolV2PacketCapacity(uuid: string) {
|
|
501
|
+
return resolveNobleProtocolV2PacketCapacity(connectedDevices.get(uuid)?.mtu);
|
|
502
|
+
},
|
|
503
|
+
|
|
445
504
|
async send(uuid: string, data: string, options?: { withoutResponse?: boolean }) {
|
|
446
505
|
const characteristics = deviceCharacteristics.get(uuid);
|
|
447
506
|
if (!characteristics) {
|
|
@@ -453,8 +512,9 @@ export function createNobleBlePlugin(): LowlevelTransportSharedPlugin {
|
|
|
453
512
|
|
|
454
513
|
const buffer = Buffer.from(data, 'hex');
|
|
455
514
|
const withoutResponse = options?.withoutResponse ?? true;
|
|
456
|
-
|
|
457
|
-
|
|
515
|
+
const packetCapacity = resolveNobleProtocolV2PacketCapacity(connectedDevices.get(uuid)?.mtu);
|
|
516
|
+
for (let offset = 0; offset < buffer.length; offset += packetCapacity) {
|
|
517
|
+
const chunk = buffer.subarray(offset, Math.min(offset + packetCapacity, buffer.length));
|
|
458
518
|
await writeCharacteristic(characteristics.write, chunk, withoutResponse);
|
|
459
519
|
}
|
|
460
520
|
},
|