@onekeyfe/hd-transport-electron 1.1.27-alpha.40 → 1.1.27-alpha.5
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/dist/ble-ops.d.ts +6 -1
- package/dist/ble-ops.d.ts.map +1 -1
- package/dist/{index-02fc07fe.js → index-de36348d.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-94c42077.js → noble-ble-handler-2e41f27c.js} +116 -72
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/ble-ops.ts +20 -3
- package/src/noble-ble-handler.ts +159 -94
package/dist/ble-ops.d.ts
CHANGED
|
@@ -7,7 +7,12 @@ export interface SoftRefreshParams {
|
|
|
7
7
|
subscriptionOperations: Map<string, 'subscribing' | 'unsubscribing' | 'idle'>;
|
|
8
8
|
subscribedDevices: Map<string, boolean>;
|
|
9
9
|
pairedDevices: Set<string>;
|
|
10
|
-
|
|
10
|
+
notificationCallbacks: Map<string, (hex: string) => void>;
|
|
11
|
+
processNotificationData: (deviceId: string, data: Buffer) => {
|
|
12
|
+
isComplete: boolean;
|
|
13
|
+
completePacket?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
11
16
|
logger: Logger | null;
|
|
12
17
|
}
|
|
13
18
|
export declare function softRefreshSubscription(params: SoftRefreshParams): Promise<void>;
|
package/dist/ble-ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ble-ops.d.ts","sourceRoot":"","sources":["../src/ble-ops.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG,MAAM,CAAC,CAAC;IAC9E,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,
|
|
1
|
+
{"version":3,"file":"ble-ops.d.ts","sourceRoot":"","sources":["../src/ble-ops.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG,MAAM,CAAC,CAAC;IAC9E,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;IAC1D,uBAAuB,EAAE,CACvB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,KACT;QACH,UAAU,EAAE,OAAO,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDtF"}
|
|
@@ -32,7 +32,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
32
32
|
|
|
33
33
|
function initNobleBleSupport(webContents) {
|
|
34
34
|
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-
|
|
35
|
+
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-2e41f27c.js'); });
|
|
36
36
|
setupNobleBleHandlers(webContents);
|
|
37
37
|
});
|
|
38
38
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-de36348d.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
|
+
var hdTransport = require('@onekeyfe/hd-transport');
|
|
5
6
|
var pRetry = require('p-retry');
|
|
6
7
|
|
|
7
8
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -19,7 +20,7 @@ function safeLog(logger, level, message, ...args) {
|
|
|
19
20
|
|
|
20
21
|
function softRefreshSubscription(params) {
|
|
21
22
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
22
|
-
const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices,
|
|
23
|
+
const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices, notificationCallbacks, processNotificationData, logger, } = params;
|
|
23
24
|
if (!notifyCharacteristic) {
|
|
24
25
|
throw new Error(`Notify characteristic not available for device ${deviceId}`);
|
|
25
26
|
}
|
|
@@ -43,7 +44,16 @@ function softRefreshSubscription(params) {
|
|
|
43
44
|
pairedDevices.add(deviceId);
|
|
44
45
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Device paired successfully', { deviceId });
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
const result = processNotificationData(deviceId, data);
|
|
48
|
+
if (result.error) {
|
|
49
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[BLE-OPS] Packet processing error:', result.error);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (result.isComplete && result.completePacket) {
|
|
53
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
54
|
+
if (appCb)
|
|
55
|
+
appCb(result.completePacket);
|
|
56
|
+
}
|
|
47
57
|
});
|
|
48
58
|
subscribedDevices.set(deviceId, true);
|
|
49
59
|
subscriptionOperations.set(deviceId, 'idle');
|
|
@@ -66,15 +76,15 @@ const deviceCharacteristics = new Map();
|
|
|
66
76
|
const notificationCallbacks = new Map();
|
|
67
77
|
const subscribedDevices = new Map();
|
|
68
78
|
const subscriptionOperations = new Map();
|
|
79
|
+
const devicePacketStates = new Map();
|
|
69
80
|
const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
|
|
70
|
-
const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
|
|
71
81
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
72
82
|
const NORMALIZED_NOTIFY_UUID = '0003';
|
|
73
83
|
const BLUETOOTH_INIT_TIMEOUT = 10000;
|
|
74
|
-
const DEVICE_SCAN_TIMEOUT =
|
|
75
|
-
const FAST_SCAN_TIMEOUT =
|
|
84
|
+
const DEVICE_SCAN_TIMEOUT = 5000;
|
|
85
|
+
const FAST_SCAN_TIMEOUT = 1500;
|
|
76
86
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
77
|
-
const CONNECTION_TIMEOUT =
|
|
87
|
+
const CONNECTION_TIMEOUT = 3000;
|
|
78
88
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
79
89
|
const BLE_PACKET_SIZE = 192;
|
|
80
90
|
const UNIFIED_WRITE_DELAY = 5;
|
|
@@ -83,40 +93,70 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
83
93
|
const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
84
94
|
/status:\s*3/i,
|
|
85
95
|
];
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
const NORMALIZED_ONEKEY_SERVICE_UUIDS = new Set([
|
|
91
|
-
...ONEKEY_SERVICE_UUIDS.map(uuid => getBleUuidKey(uuid)),
|
|
92
|
-
'0001',
|
|
93
|
-
]);
|
|
94
|
-
function isGenericBleService(uuid) {
|
|
95
|
-
return ['1800', '1801', '180a', '180f'].includes(getBleUuidKey(uuid));
|
|
96
|
-
}
|
|
97
|
-
function hasOneKeyAdvertisementService(peripheral) {
|
|
98
|
-
var _a, _b;
|
|
99
|
-
const serviceUuids = (_b = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids) !== null && _b !== void 0 ? _b : [];
|
|
100
|
-
return serviceUuids.some(uuid => {
|
|
101
|
-
const uuidKey = getBleUuidKey(uuid);
|
|
102
|
-
return (NORMALIZED_ONEKEY_SERVICE_UUIDS.has(uuidKey) ||
|
|
103
|
-
PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(uuidKey));
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
function isOneKeyPeripheral(peripheral) {
|
|
107
|
-
var _a;
|
|
108
|
-
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || null;
|
|
109
|
-
return hdShared.isOnekeyDevice(deviceName, peripheral.id) || hasOneKeyAdvertisementService(peripheral);
|
|
110
|
-
}
|
|
111
|
-
function emitRawNotification(deviceId, data) {
|
|
112
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Raw notification', {
|
|
96
|
+
const MIN_HEADER_LENGTH = 9;
|
|
97
|
+
function processNotificationData(deviceId, data) {
|
|
98
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification', {
|
|
113
99
|
deviceId,
|
|
114
100
|
dataLength: data.length,
|
|
115
|
-
firstBytes: data.subarray(0, 8).toString('hex'),
|
|
116
101
|
});
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
102
|
+
let packetState = devicePacketStates.get(deviceId);
|
|
103
|
+
if (!packetState) {
|
|
104
|
+
packetState = { bufferLength: 0, buffer: [], packetCount: 0 };
|
|
105
|
+
devicePacketStates.set(deviceId, packetState);
|
|
106
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Initialized new packet state for device:', deviceId);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
if (hdShared.isHeaderChunk(data)) {
|
|
110
|
+
if (data.length < MIN_HEADER_LENGTH) {
|
|
111
|
+
return { isComplete: false, error: 'Invalid header chunk: too short' };
|
|
112
|
+
}
|
|
113
|
+
const messageId = `${deviceId}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
114
|
+
packetState.bufferLength = data.readInt32BE(5);
|
|
115
|
+
packetState.buffer = [...data.subarray(3)];
|
|
116
|
+
packetState.packetCount = 1;
|
|
117
|
+
packetState.messageId = messageId;
|
|
118
|
+
if (packetState.bufferLength < 0) {
|
|
119
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Invalid negative packet length detected:', {
|
|
120
|
+
length: packetState.bufferLength,
|
|
121
|
+
dataLength: data.length,
|
|
122
|
+
rawHeader: data.subarray(0, Math.min(16, data.length)).toString('hex'),
|
|
123
|
+
lengthBytes: data.subarray(5, 9).toString('hex'),
|
|
124
|
+
});
|
|
125
|
+
resetPacketState(packetState);
|
|
126
|
+
return { isComplete: false, error: 'Invalid packet length in header' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
if (packetState.bufferLength === 0) {
|
|
131
|
+
return { isComplete: false, error: 'Received data chunk without header' };
|
|
132
|
+
}
|
|
133
|
+
packetState.packetCount += 1;
|
|
134
|
+
packetState.buffer = packetState.buffer.concat([...data]);
|
|
135
|
+
}
|
|
136
|
+
if (packetState.buffer.length - hdTransport.COMMON_HEADER_SIZE >= packetState.bufferLength) {
|
|
137
|
+
const completeBuffer = Buffer.from(packetState.buffer);
|
|
138
|
+
const hexString = completeBuffer.toString('hex');
|
|
139
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Packet assembled', {
|
|
140
|
+
deviceId,
|
|
141
|
+
totalPackets: packetState.packetCount,
|
|
142
|
+
expectedLength: packetState.bufferLength,
|
|
143
|
+
actualLength: packetState.buffer.length - hdTransport.COMMON_HEADER_SIZE,
|
|
144
|
+
});
|
|
145
|
+
resetPacketState(packetState);
|
|
146
|
+
return { isComplete: true, completePacket: hexString };
|
|
147
|
+
}
|
|
148
|
+
return { isComplete: false };
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
resetPacketState(packetState);
|
|
152
|
+
return { isComplete: false, error: `Packet processing error: ${error}` };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function resetPacketState(packetState) {
|
|
156
|
+
packetState.bufferLength = 0;
|
|
157
|
+
packetState.buffer = [];
|
|
158
|
+
packetState.packetCount = 0;
|
|
159
|
+
packetState.messageId = undefined;
|
|
120
160
|
}
|
|
121
161
|
function checkBluetoothAvailability() {
|
|
122
162
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -161,6 +201,7 @@ function setupPersistentStateListener() {
|
|
|
161
201
|
deviceCharacteristics.clear();
|
|
162
202
|
subscribedDevices.clear();
|
|
163
203
|
notificationCallbacks.clear();
|
|
204
|
+
devicePacketStates.clear();
|
|
164
205
|
subscriptionOperations.clear();
|
|
165
206
|
pairedDevices.clear();
|
|
166
207
|
if (noble) {
|
|
@@ -280,6 +321,7 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
280
321
|
connectedDevices.delete(deviceId);
|
|
281
322
|
deviceCharacteristics.delete(deviceId);
|
|
282
323
|
notificationCallbacks.delete(deviceId);
|
|
324
|
+
devicePacketStates.delete(deviceId);
|
|
283
325
|
subscribedDevices.delete(deviceId);
|
|
284
326
|
subscriptionOperations.delete(deviceId);
|
|
285
327
|
pairedDevices.delete(deviceId);
|
|
@@ -394,7 +436,8 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
394
436
|
subscriptionOperations,
|
|
395
437
|
subscribedDevices,
|
|
396
438
|
pairedDevices,
|
|
397
|
-
|
|
439
|
+
notificationCallbacks,
|
|
440
|
+
processNotificationData,
|
|
398
441
|
logger,
|
|
399
442
|
});
|
|
400
443
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -463,21 +506,13 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
463
506
|
});
|
|
464
507
|
}
|
|
465
508
|
function handleDeviceDiscovered(peripheral) {
|
|
466
|
-
var _a
|
|
509
|
+
var _a;
|
|
467
510
|
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
468
|
-
|
|
469
|
-
if (!isOneKeyPeripheral(peripheral)) {
|
|
511
|
+
if (!hdShared.isOnekeyDevice(deviceName)) {
|
|
470
512
|
return;
|
|
471
513
|
}
|
|
472
|
-
|
|
514
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered OneKey device:', deviceName);
|
|
473
515
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
474
|
-
if (isNewDevice) {
|
|
475
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered OneKey BLE device:', {
|
|
476
|
-
name: deviceName,
|
|
477
|
-
id: peripheral.id,
|
|
478
|
-
serviceUUIDs: serviceUuids,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
516
|
}
|
|
482
517
|
function ensureDiscoverListener() {
|
|
483
518
|
if (!noble)
|
|
@@ -522,7 +557,7 @@ function performTargetedScan(targetDeviceId) {
|
|
|
522
557
|
resolve(null);
|
|
523
558
|
}, FAST_SCAN_TIMEOUT);
|
|
524
559
|
nobleInstance.on('discover', onDiscover);
|
|
525
|
-
nobleInstance.startScanning(
|
|
560
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error) => {
|
|
526
561
|
if (error) {
|
|
527
562
|
clearTimeout(timeoutId);
|
|
528
563
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -572,13 +607,11 @@ function enumerateDevices() {
|
|
|
572
607
|
});
|
|
573
608
|
};
|
|
574
609
|
const timeoutId = setTimeout(() => {
|
|
575
|
-
checkDevices();
|
|
576
610
|
cleanup();
|
|
577
611
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
578
612
|
resolve(devices);
|
|
579
613
|
}, DEVICE_SCAN_TIMEOUT);
|
|
580
|
-
|
|
581
|
-
nobleInstance.startScanning([], false, (error) => {
|
|
614
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error) => {
|
|
582
615
|
if (error) {
|
|
583
616
|
cleanup();
|
|
584
617
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -673,7 +706,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
673
706
|
});
|
|
674
707
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
675
708
|
const services = yield new Promise((resolve, reject) => {
|
|
676
|
-
peripheral.discoverServices(
|
|
709
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
677
710
|
if (error) {
|
|
678
711
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
|
|
679
712
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -683,25 +716,13 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
683
716
|
}
|
|
684
717
|
});
|
|
685
718
|
});
|
|
686
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] All services:', services === null || services === void 0 ? void 0 : services.map(s => s.uuid));
|
|
687
719
|
if (!services || services.length === 0) {
|
|
688
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
689
|
-
}
|
|
690
|
-
let service = services.find(s => NORMALIZED_ONEKEY_SERVICE_UUIDS.has(getBleUuidKey(s.uuid)));
|
|
691
|
-
if (!service) {
|
|
692
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Known OneKey service UUID not found, trying first vendor service');
|
|
693
|
-
service =
|
|
694
|
-
services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
|
|
695
|
-
services.find(s => !isGenericBleService(s.uuid)) ||
|
|
696
|
-
services[0];
|
|
697
|
-
}
|
|
698
|
-
if (!service) {
|
|
699
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
720
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
700
721
|
}
|
|
701
|
-
const
|
|
702
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE]
|
|
722
|
+
const service = services[0];
|
|
723
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found service:', service.uuid);
|
|
703
724
|
const characteristics = yield new Promise((resolve, reject) => {
|
|
704
|
-
|
|
725
|
+
service.discoverCharacteristics([hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID, hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID], (error, chars) => {
|
|
705
726
|
if (error) {
|
|
706
727
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
707
728
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
@@ -923,6 +944,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
923
944
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
924
945
|
}
|
|
925
946
|
notificationCallbacks.delete(deviceId);
|
|
947
|
+
devicePacketStates.delete(deviceId);
|
|
926
948
|
subscribedDevices.delete(deviceId);
|
|
927
949
|
}
|
|
928
950
|
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
@@ -1005,6 +1027,7 @@ function unsubscribeNotifications(deviceId) {
|
|
|
1005
1027
|
});
|
|
1006
1028
|
notifyCharacteristic.removeAllListeners('data');
|
|
1007
1029
|
notificationCallbacks.delete(deviceId);
|
|
1030
|
+
devicePacketStates.delete(deviceId);
|
|
1008
1031
|
subscribedDevices.delete(deviceId);
|
|
1009
1032
|
}
|
|
1010
1033
|
finally {
|
|
@@ -1044,6 +1067,12 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1044
1067
|
if (subscribedDevices.get(deviceId)) {
|
|
1045
1068
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already subscribed to characteristic, updating callback only');
|
|
1046
1069
|
notificationCallbacks.set(deviceId, callback);
|
|
1070
|
+
devicePacketStates.set(deviceId, {
|
|
1071
|
+
bufferLength: 0,
|
|
1072
|
+
buffer: [],
|
|
1073
|
+
packetCount: 0,
|
|
1074
|
+
messageId: undefined,
|
|
1075
|
+
});
|
|
1047
1076
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1048
1077
|
return Promise.resolve();
|
|
1049
1078
|
}
|
|
@@ -1052,6 +1081,12 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1052
1081
|
}
|
|
1053
1082
|
notifyCharacteristic.removeAllListeners('data');
|
|
1054
1083
|
notificationCallbacks.set(deviceId, callback);
|
|
1084
|
+
devicePacketStates.set(deviceId, {
|
|
1085
|
+
bufferLength: 0,
|
|
1086
|
+
buffer: [],
|
|
1087
|
+
packetCount: 0,
|
|
1088
|
+
messageId: undefined,
|
|
1089
|
+
});
|
|
1055
1090
|
function rebuildAppSubscription(deviceId, notifyCharacteristic) {
|
|
1056
1091
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1057
1092
|
yield new Promise(resolve => {
|
|
@@ -1073,7 +1108,16 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1073
1108
|
pairedDevices.add(deviceId);
|
|
1074
1109
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1075
1110
|
}
|
|
1076
|
-
|
|
1111
|
+
const result = processNotificationData(deviceId, data);
|
|
1112
|
+
if (result.error) {
|
|
1113
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Packet processing error:', result.error);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (result.isComplete && result.completePacket) {
|
|
1117
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
1118
|
+
if (appCb)
|
|
1119
|
+
appCb(result.completePacket);
|
|
1120
|
+
}
|
|
1077
1121
|
});
|
|
1078
1122
|
});
|
|
1079
1123
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAshDhE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAsKpE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-electron",
|
|
3
|
-
"version": "1.1.27-alpha.
|
|
3
|
+
"version": "1.1.27-alpha.5",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"electron-log": ">=4.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-core": "1.1.27-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.1.27-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.1.27-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.1.27-alpha.5",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.1.27-alpha.5",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.1.27-alpha.5",
|
|
31
31
|
"@stoprocent/noble": "2.3.16",
|
|
32
32
|
"p-retry": "^4.6.2"
|
|
33
33
|
},
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"electron": "^25.0.0",
|
|
37
37
|
"typescript": "^5.3.3"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "0c7353e097121596ad4076a531a473408471674c"
|
|
40
40
|
}
|
package/src/ble-ops.ts
CHANGED
|
@@ -7,7 +7,15 @@ export interface SoftRefreshParams {
|
|
|
7
7
|
subscriptionOperations: Map<string, 'subscribing' | 'unsubscribing' | 'idle'>;
|
|
8
8
|
subscribedDevices: Map<string, boolean>;
|
|
9
9
|
pairedDevices: Set<string>;
|
|
10
|
-
|
|
10
|
+
notificationCallbacks: Map<string, (hex: string) => void>;
|
|
11
|
+
processNotificationData: (
|
|
12
|
+
deviceId: string,
|
|
13
|
+
data: Buffer
|
|
14
|
+
) => {
|
|
15
|
+
isComplete: boolean;
|
|
16
|
+
completePacket?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
};
|
|
11
19
|
logger: Logger | null;
|
|
12
20
|
}
|
|
13
21
|
|
|
@@ -18,7 +26,8 @@ export async function softRefreshSubscription(params: SoftRefreshParams): Promis
|
|
|
18
26
|
subscriptionOperations,
|
|
19
27
|
subscribedDevices,
|
|
20
28
|
pairedDevices,
|
|
21
|
-
|
|
29
|
+
notificationCallbacks,
|
|
30
|
+
processNotificationData,
|
|
22
31
|
logger,
|
|
23
32
|
} = params;
|
|
24
33
|
|
|
@@ -51,7 +60,15 @@ export async function softRefreshSubscription(params: SoftRefreshParams): Promis
|
|
|
51
60
|
logger?.info('[BLE-OPS] Device paired successfully', { deviceId });
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
|
|
63
|
+
const result = processNotificationData(deviceId, data);
|
|
64
|
+
if (result.error) {
|
|
65
|
+
logger?.error('[BLE-OPS] Packet processing error:', result.error);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (result.isComplete && result.completePacket) {
|
|
69
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
70
|
+
if (appCb) appCb(result.completePacket);
|
|
71
|
+
}
|
|
55
72
|
});
|
|
56
73
|
|
|
57
74
|
subscribedDevices.set(deviceId, true);
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -9,10 +9,14 @@ import {
|
|
|
9
9
|
EOneKeyBleMessageKeys,
|
|
10
10
|
ERRORS,
|
|
11
11
|
HardwareErrorCode,
|
|
12
|
+
ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
|
|
12
13
|
ONEKEY_SERVICE_UUID,
|
|
14
|
+
ONEKEY_WRITE_CHARACTERISTIC_UUID,
|
|
15
|
+
isHeaderChunk,
|
|
13
16
|
isOnekeyDevice,
|
|
14
17
|
wait,
|
|
15
18
|
} from '@onekeyfe/hd-shared';
|
|
19
|
+
import { COMMON_HEADER_SIZE } from '@onekeyfe/hd-transport';
|
|
16
20
|
import pRetry from 'p-retry';
|
|
17
21
|
|
|
18
22
|
import { safeLog } from './types/noble-extended';
|
|
@@ -51,6 +55,15 @@ const subscribedDevices = new Map<string, boolean>(); // Track subscription stat
|
|
|
51
55
|
// 🔒 Subscription operation state tracking to prevent race conditions
|
|
52
56
|
const subscriptionOperations = new Map<string, 'subscribing' | 'unsubscribing' | 'idle'>();
|
|
53
57
|
|
|
58
|
+
// Packet reassembly state for each device
|
|
59
|
+
interface PacketAssemblyState {
|
|
60
|
+
bufferLength: number;
|
|
61
|
+
buffer: number[];
|
|
62
|
+
packetCount: number;
|
|
63
|
+
messageId?: string; // Add message ID to track concurrent requests
|
|
64
|
+
}
|
|
65
|
+
const devicePacketStates = new Map<string, PacketAssemblyState>();
|
|
66
|
+
|
|
54
67
|
// Windows-only response watchdog state moved to utils/windows-ble-recovery
|
|
55
68
|
|
|
56
69
|
// Pairing-related state removed
|
|
@@ -59,7 +72,6 @@ const subscriptionOperations = new Map<string, 'subscribing' | 'unsubscribing' |
|
|
|
59
72
|
|
|
60
73
|
// Service UUIDs to scan for - using constants from hd-shared
|
|
61
74
|
const ONEKEY_SERVICE_UUIDS = [ONEKEY_SERVICE_UUID];
|
|
62
|
-
const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
|
|
63
75
|
|
|
64
76
|
// Pre-normalized characteristic identifiers for fast comparison
|
|
65
77
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
@@ -67,10 +79,10 @@ const NORMALIZED_NOTIFY_UUID = '0003';
|
|
|
67
79
|
|
|
68
80
|
// Timeout and interval constants
|
|
69
81
|
const BLUETOOTH_INIT_TIMEOUT = 10000; // 10 seconds for Bluetooth initialization
|
|
70
|
-
const DEVICE_SCAN_TIMEOUT =
|
|
71
|
-
const FAST_SCAN_TIMEOUT =
|
|
82
|
+
const DEVICE_SCAN_TIMEOUT = 5000; // 5 seconds for device scanning
|
|
83
|
+
const FAST_SCAN_TIMEOUT = 1500; // 1.5 seconds for fast targeted scanning
|
|
72
84
|
const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
|
|
73
|
-
const CONNECTION_TIMEOUT =
|
|
85
|
+
const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
|
|
74
86
|
const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
|
|
75
87
|
|
|
76
88
|
// Write-related constants
|
|
@@ -82,49 +94,101 @@ const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
|
82
94
|
/status:\s*3/i, // Windows pairing cancelled / GATT write failed
|
|
83
95
|
];
|
|
84
96
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const NORMALIZED_ONEKEY_SERVICE_UUIDS = new Set([
|
|
91
|
-
...ONEKEY_SERVICE_UUIDS.map(uuid => getBleUuidKey(uuid)),
|
|
92
|
-
'0001',
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
|
-
function isGenericBleService(uuid?: string | null) {
|
|
96
|
-
return ['1800', '1801', '180a', '180f'].includes(getBleUuidKey(uuid));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function hasOneKeyAdvertisementService(peripheral: Peripheral) {
|
|
100
|
-
const serviceUuids = peripheral.advertisement?.serviceUuids ?? [];
|
|
101
|
-
return serviceUuids.some(uuid => {
|
|
102
|
-
const uuidKey = getBleUuidKey(uuid);
|
|
103
|
-
return (
|
|
104
|
-
NORMALIZED_ONEKEY_SERVICE_UUIDS.has(uuidKey) ||
|
|
105
|
-
PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(uuidKey)
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
}
|
|
97
|
+
// Validation limits
|
|
98
|
+
const MIN_HEADER_LENGTH = 9; // Minimum header chunk length
|
|
109
99
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
// Packet processing result types
|
|
101
|
+
interface PacketProcessResult {
|
|
102
|
+
isComplete: boolean;
|
|
103
|
+
completePacket?: string;
|
|
104
|
+
error?: string;
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function emitRawNotification(deviceId: string, data: Buffer): void {
|
|
120
|
-
logger?.info('[NobleBLE] Raw notification', {
|
|
107
|
+
// Process incoming BLE notification data with proper packet reassembly
|
|
108
|
+
function processNotificationData(deviceId: string, data: Buffer): PacketProcessResult {
|
|
109
|
+
// notification telemetry
|
|
110
|
+
logger?.info('[NobleBLE] Notification', {
|
|
121
111
|
deviceId,
|
|
122
112
|
dataLength: data.length,
|
|
123
|
-
firstBytes: data.subarray(0, 8).toString('hex'),
|
|
124
113
|
});
|
|
125
114
|
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
// Get or initialize packet state for this device
|
|
116
|
+
let packetState = devicePacketStates.get(deviceId);
|
|
117
|
+
if (!packetState) {
|
|
118
|
+
packetState = { bufferLength: 0, buffer: [], packetCount: 0 };
|
|
119
|
+
devicePacketStates.set(deviceId, packetState);
|
|
120
|
+
logger?.info('[NobleBLE] Initialized new packet state for device:', deviceId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
if (isHeaderChunk(data)) {
|
|
125
|
+
// Validate header chunk
|
|
126
|
+
if (data.length < MIN_HEADER_LENGTH) {
|
|
127
|
+
return { isComplete: false, error: 'Invalid header chunk: too short' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Generate message ID for this packet sequence
|
|
131
|
+
const messageId = `${deviceId}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
132
|
+
|
|
133
|
+
// Reset packet state for new message
|
|
134
|
+
packetState.bufferLength = data.readInt32BE(5);
|
|
135
|
+
packetState.buffer = [...data.subarray(3)];
|
|
136
|
+
packetState.packetCount = 1;
|
|
137
|
+
packetState.messageId = messageId;
|
|
138
|
+
|
|
139
|
+
// Only validate for negative lengths (which would be invalid)
|
|
140
|
+
if (packetState.bufferLength < 0) {
|
|
141
|
+
logger?.error('[NobleBLE] Invalid negative packet length detected:', {
|
|
142
|
+
length: packetState.bufferLength,
|
|
143
|
+
dataLength: data.length,
|
|
144
|
+
rawHeader: data.subarray(0, Math.min(16, data.length)).toString('hex'),
|
|
145
|
+
lengthBytes: data.subarray(5, 9).toString('hex'),
|
|
146
|
+
});
|
|
147
|
+
resetPacketState(packetState);
|
|
148
|
+
return { isComplete: false, error: 'Invalid packet length in header' };
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// Validate we have an active packet session
|
|
152
|
+
if (packetState.bufferLength === 0) {
|
|
153
|
+
return { isComplete: false, error: 'Received data chunk without header' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Increment packet counter and append data
|
|
157
|
+
packetState.packetCount += 1;
|
|
158
|
+
packetState.buffer = packetState.buffer.concat([...data]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check if packet is complete
|
|
162
|
+
if (packetState.buffer.length - COMMON_HEADER_SIZE >= packetState.bufferLength) {
|
|
163
|
+
const completeBuffer = Buffer.from(packetState.buffer);
|
|
164
|
+
const hexString = completeBuffer.toString('hex');
|
|
165
|
+
|
|
166
|
+
logger?.info('[NobleBLE] Packet assembled', {
|
|
167
|
+
deviceId,
|
|
168
|
+
totalPackets: packetState.packetCount,
|
|
169
|
+
expectedLength: packetState.bufferLength,
|
|
170
|
+
actualLength: packetState.buffer.length - COMMON_HEADER_SIZE,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Reset packet state for next message
|
|
174
|
+
resetPacketState(packetState);
|
|
175
|
+
|
|
176
|
+
return { isComplete: true, completePacket: hexString };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { isComplete: false };
|
|
180
|
+
} catch (error) {
|
|
181
|
+
resetPacketState(packetState);
|
|
182
|
+
return { isComplete: false, error: `Packet processing error: ${error}` };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Reset packet state to clean state
|
|
187
|
+
function resetPacketState(packetState: PacketAssemblyState): void {
|
|
188
|
+
packetState.bufferLength = 0;
|
|
189
|
+
packetState.buffer = [];
|
|
190
|
+
packetState.packetCount = 0;
|
|
191
|
+
packetState.messageId = undefined;
|
|
128
192
|
}
|
|
129
193
|
|
|
130
194
|
// Check Bluetooth availability - returns detailed state
|
|
@@ -186,6 +250,7 @@ function setupPersistentStateListener(): void {
|
|
|
186
250
|
deviceCharacteristics.clear();
|
|
187
251
|
subscribedDevices.clear();
|
|
188
252
|
notificationCallbacks.clear();
|
|
253
|
+
devicePacketStates.clear();
|
|
189
254
|
subscriptionOperations.clear();
|
|
190
255
|
pairedDevices.clear();
|
|
191
256
|
|
|
@@ -363,6 +428,7 @@ function cleanupDevice(
|
|
|
363
428
|
connectedDevices.delete(deviceId);
|
|
364
429
|
deviceCharacteristics.delete(deviceId);
|
|
365
430
|
notificationCallbacks.delete(deviceId);
|
|
431
|
+
devicePacketStates.delete(deviceId);
|
|
366
432
|
subscribedDevices.delete(deviceId);
|
|
367
433
|
subscriptionOperations.delete(deviceId);
|
|
368
434
|
pairedDevices.delete(deviceId);
|
|
@@ -524,7 +590,8 @@ async function attemptWindowsWriteUntilPaired(
|
|
|
524
590
|
subscriptionOperations,
|
|
525
591
|
subscribedDevices,
|
|
526
592
|
pairedDevices,
|
|
527
|
-
|
|
593
|
+
notificationCallbacks,
|
|
594
|
+
processNotificationData,
|
|
528
595
|
logger,
|
|
529
596
|
});
|
|
530
597
|
logger?.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -619,23 +686,14 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
619
686
|
// Handle discovered device (for general enumeration only)
|
|
620
687
|
function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
621
688
|
const deviceName = peripheral.advertisement?.localName || 'Unknown Device';
|
|
622
|
-
const serviceUuids = peripheral.advertisement?.serviceUuids || [];
|
|
623
689
|
|
|
624
|
-
// Only process OneKey
|
|
625
|
-
|
|
626
|
-
if (!isOneKeyPeripheral(peripheral)) {
|
|
690
|
+
// Only process OneKey devices for general discovery
|
|
691
|
+
if (!isOnekeyDevice(deviceName)) {
|
|
627
692
|
return;
|
|
628
693
|
}
|
|
629
694
|
|
|
630
|
-
|
|
695
|
+
logger?.info('[NobleBLE] Discovered OneKey device:', deviceName);
|
|
631
696
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
632
|
-
if (isNewDevice) {
|
|
633
|
-
logger?.info('[NobleBLE] Discovered OneKey BLE device:', {
|
|
634
|
-
name: deviceName,
|
|
635
|
-
id: peripheral.id,
|
|
636
|
-
serviceUUIDs: serviceUuids,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
697
|
}
|
|
640
698
|
|
|
641
699
|
// Ensure discover listener is properly set up
|
|
@@ -695,8 +753,8 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
|
|
|
695
753
|
// Add local listener for this scan
|
|
696
754
|
nobleInstance.on('discover', onDiscover);
|
|
697
755
|
|
|
698
|
-
// Start scanning
|
|
699
|
-
nobleInstance.startScanning(
|
|
756
|
+
// Start scanning
|
|
757
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
|
|
700
758
|
if (error) {
|
|
701
759
|
clearTimeout(timeoutId);
|
|
702
760
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -758,19 +816,15 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
|
|
|
758
816
|
});
|
|
759
817
|
};
|
|
760
818
|
|
|
761
|
-
// Set timeout for scanning
|
|
819
|
+
// Set timeout for scanning
|
|
762
820
|
const timeoutId = setTimeout(() => {
|
|
763
|
-
// Final collection before resolving — catches devices discovered near the deadline
|
|
764
|
-
checkDevices();
|
|
765
821
|
cleanup();
|
|
766
822
|
logger?.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
767
823
|
resolve(devices);
|
|
768
824
|
}, DEVICE_SCAN_TIMEOUT);
|
|
769
825
|
|
|
770
|
-
// Start scanning
|
|
771
|
-
|
|
772
|
-
logger?.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
773
|
-
nobleInstance.startScanning([], false, (error?: Error) => {
|
|
826
|
+
// Start scanning for OneKey service UUIDs
|
|
827
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
|
|
774
828
|
if (error) {
|
|
775
829
|
cleanup();
|
|
776
830
|
logger?.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -898,9 +952,9 @@ async function discoverServicesAndCharacteristics(
|
|
|
898
952
|
|
|
899
953
|
// Main discovery logic as async function
|
|
900
954
|
const discoveryPromise = (async (): Promise<CharacteristicPair> => {
|
|
901
|
-
// Step 1: Discover
|
|
955
|
+
// Step 1: Discover services (promisified)
|
|
902
956
|
const services = await new Promise<Service[]>((resolve, reject) => {
|
|
903
|
-
peripheral.discoverServices(
|
|
957
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
904
958
|
if (error) {
|
|
905
959
|
logger?.error('[NobleBLE] Service discovery failed:', error);
|
|
906
960
|
reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -910,41 +964,26 @@ async function discoverServicesAndCharacteristics(
|
|
|
910
964
|
});
|
|
911
965
|
});
|
|
912
966
|
|
|
913
|
-
// Log all discovered services
|
|
914
|
-
logger?.info(
|
|
915
|
-
'[NobleBLE] All services:',
|
|
916
|
-
services?.map(s => s.uuid)
|
|
917
|
-
);
|
|
918
|
-
|
|
919
967
|
if (!services || services.length === 0) {
|
|
920
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
968
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
921
969
|
}
|
|
922
970
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (!service) {
|
|
926
|
-
logger?.info('[NobleBLE] Known OneKey service UUID not found, trying first vendor service');
|
|
927
|
-
service =
|
|
928
|
-
services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
|
|
929
|
-
services.find(s => !isGenericBleService(s.uuid)) ||
|
|
930
|
-
services[0];
|
|
931
|
-
}
|
|
932
|
-
if (!service) {
|
|
933
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
934
|
-
}
|
|
935
|
-
const selectedService = service;
|
|
936
|
-
logger?.info('[NobleBLE] Using service:', selectedService.uuid);
|
|
971
|
+
const service = services[0];
|
|
972
|
+
logger?.info('[NobleBLE] Found service:', service.uuid);
|
|
937
973
|
|
|
938
|
-
// Step 2: Discover
|
|
974
|
+
// Step 2: Discover characteristics (promisified)
|
|
939
975
|
const characteristics = await new Promise<Characteristic[]>((resolve, reject) => {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
976
|
+
service.discoverCharacteristics(
|
|
977
|
+
[ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
|
|
978
|
+
(error, chars) => {
|
|
979
|
+
if (error) {
|
|
980
|
+
logger?.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
981
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
982
|
+
} else {
|
|
983
|
+
resolve(chars);
|
|
984
|
+
}
|
|
946
985
|
}
|
|
947
|
-
|
|
986
|
+
);
|
|
948
987
|
});
|
|
949
988
|
|
|
950
989
|
// Step 3: Find required characteristics
|
|
@@ -1281,6 +1320,7 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1281
1320
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
1282
1321
|
}
|
|
1283
1322
|
notificationCallbacks.delete(deviceId);
|
|
1323
|
+
devicePacketStates.delete(deviceId);
|
|
1284
1324
|
subscribedDevices.delete(deviceId);
|
|
1285
1325
|
// Continue to re-setup the connection properly
|
|
1286
1326
|
}
|
|
@@ -1391,6 +1431,7 @@ async function unsubscribeNotifications(deviceId: string): Promise<void> {
|
|
|
1391
1431
|
// Remove all listeners and clear subscription status
|
|
1392
1432
|
notifyCharacteristic.removeAllListeners('data');
|
|
1393
1433
|
notificationCallbacks.delete(deviceId);
|
|
1434
|
+
devicePacketStates.delete(deviceId);
|
|
1394
1435
|
subscribedDevices.delete(deviceId);
|
|
1395
1436
|
} finally {
|
|
1396
1437
|
// 🔒 CRITICAL: Always clear operation state (even on error)
|
|
@@ -1457,6 +1498,14 @@ async function subscribeNotifications(
|
|
|
1457
1498
|
// Just update the callback without re-subscribing
|
|
1458
1499
|
notificationCallbacks.set(deviceId, callback);
|
|
1459
1500
|
|
|
1501
|
+
// Reset packet state for new session
|
|
1502
|
+
devicePacketStates.set(deviceId, {
|
|
1503
|
+
bufferLength: 0,
|
|
1504
|
+
buffer: [],
|
|
1505
|
+
packetCount: 0,
|
|
1506
|
+
messageId: undefined,
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1460
1509
|
// 🔒 Clear operation state
|
|
1461
1510
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1462
1511
|
return Promise.resolve();
|
|
@@ -1473,6 +1522,14 @@ async function subscribeNotifications(
|
|
|
1473
1522
|
// Store callback for this device
|
|
1474
1523
|
notificationCallbacks.set(deviceId, callback);
|
|
1475
1524
|
|
|
1525
|
+
// Reset packet state for new subscription session
|
|
1526
|
+
devicePacketStates.set(deviceId, {
|
|
1527
|
+
bufferLength: 0,
|
|
1528
|
+
buffer: [],
|
|
1529
|
+
packetCount: 0,
|
|
1530
|
+
messageId: undefined,
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1476
1533
|
// Helper: rebuild a clean application-layer subscription
|
|
1477
1534
|
async function rebuildAppSubscription(
|
|
1478
1535
|
deviceId: string,
|
|
@@ -1501,7 +1558,15 @@ async function subscribeNotifications(
|
|
|
1501
1558
|
logger?.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1502
1559
|
}
|
|
1503
1560
|
|
|
1504
|
-
|
|
1561
|
+
const result = processNotificationData(deviceId, data);
|
|
1562
|
+
if (result.error) {
|
|
1563
|
+
logger?.error('[NobleBLE] Packet processing error:', result.error);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (result.isComplete && result.completePacket) {
|
|
1567
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
1568
|
+
if (appCb) appCb(result.completePacket);
|
|
1569
|
+
}
|
|
1505
1570
|
});
|
|
1506
1571
|
}
|
|
1507
1572
|
|