@onekeyfe/hd-transport-electron 1.1.34-alpha.3 → 1.1.34-alpha.4
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-55699e30.js → index-de36348d.js} +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-16791965.js → noble-ble-handler-2e41f27c.js} +138 -83
- 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 +186 -99
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.d.ts
CHANGED
|
@@ -26,10 +26,6 @@ interface Logger {
|
|
|
26
26
|
error(message: string, ...args: any[]): void;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Desktop API types for Electron preload script
|
|
31
|
-
* These types define the core interface for Noble BLE communication
|
|
32
|
-
*/
|
|
33
29
|
interface NobleBleAPI {
|
|
34
30
|
enumerate: () => Promise<{
|
|
35
31
|
id: string;
|
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,35 +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
|
-
|
|
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));
|
|
96
|
+
const MIN_HEADER_LENGTH = 9;
|
|
97
|
+
function processNotificationData(deviceId, data) {
|
|
98
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification', {
|
|
99
|
+
deviceId,
|
|
100
|
+
dataLength: data.length,
|
|
104
101
|
});
|
|
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
|
+
}
|
|
105
154
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
function emitRawNotification(deviceId, data) {
|
|
112
|
-
const appCb = notificationCallbacks.get(deviceId);
|
|
113
|
-
if (appCb)
|
|
114
|
-
appCb(data.toString('hex'));
|
|
155
|
+
function resetPacketState(packetState) {
|
|
156
|
+
packetState.bufferLength = 0;
|
|
157
|
+
packetState.buffer = [];
|
|
158
|
+
packetState.packetCount = 0;
|
|
159
|
+
packetState.messageId = undefined;
|
|
115
160
|
}
|
|
116
161
|
function checkBluetoothAvailability() {
|
|
117
162
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -156,6 +201,7 @@ function setupPersistentStateListener() {
|
|
|
156
201
|
deviceCharacteristics.clear();
|
|
157
202
|
subscribedDevices.clear();
|
|
158
203
|
notificationCallbacks.clear();
|
|
204
|
+
devicePacketStates.clear();
|
|
159
205
|
subscriptionOperations.clear();
|
|
160
206
|
pairedDevices.clear();
|
|
161
207
|
if (noble) {
|
|
@@ -275,6 +321,7 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
275
321
|
connectedDevices.delete(deviceId);
|
|
276
322
|
deviceCharacteristics.delete(deviceId);
|
|
277
323
|
notificationCallbacks.delete(deviceId);
|
|
324
|
+
devicePacketStates.delete(deviceId);
|
|
278
325
|
subscribedDevices.delete(deviceId);
|
|
279
326
|
subscriptionOperations.delete(deviceId);
|
|
280
327
|
pairedDevices.delete(deviceId);
|
|
@@ -389,7 +436,8 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
389
436
|
subscriptionOperations,
|
|
390
437
|
subscribedDevices,
|
|
391
438
|
pairedDevices,
|
|
392
|
-
|
|
439
|
+
notificationCallbacks,
|
|
440
|
+
processNotificationData,
|
|
393
441
|
logger,
|
|
394
442
|
});
|
|
395
443
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -410,6 +458,11 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
410
458
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
|
|
411
459
|
}
|
|
412
460
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
461
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Writing data:', {
|
|
462
|
+
deviceId,
|
|
463
|
+
dataLength: toBuffer.length,
|
|
464
|
+
firstBytes: toBuffer.subarray(0, 8).toString('hex'),
|
|
465
|
+
});
|
|
413
466
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
414
467
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
415
468
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
@@ -453,19 +506,13 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
453
506
|
});
|
|
454
507
|
}
|
|
455
508
|
function handleDeviceDiscovered(peripheral) {
|
|
456
|
-
var _a
|
|
457
|
-
|
|
509
|
+
var _a;
|
|
510
|
+
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
511
|
+
if (!hdShared.isOnekeyDevice(deviceName)) {
|
|
458
512
|
return;
|
|
459
513
|
}
|
|
460
|
-
|
|
514
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered OneKey device:', deviceName);
|
|
461
515
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
462
|
-
if (isNewDevice) {
|
|
463
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
464
|
-
deviceId: peripheral.id,
|
|
465
|
-
name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
|
|
466
|
-
serviceUUIDs: ((_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.serviceUuids) || [],
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
516
|
}
|
|
470
517
|
function ensureDiscoverListener() {
|
|
471
518
|
if (!noble)
|
|
@@ -510,7 +557,7 @@ function performTargetedScan(targetDeviceId) {
|
|
|
510
557
|
resolve(null);
|
|
511
558
|
}, FAST_SCAN_TIMEOUT);
|
|
512
559
|
nobleInstance.on('discover', onDiscover);
|
|
513
|
-
nobleInstance.startScanning(
|
|
560
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error) => {
|
|
514
561
|
if (error) {
|
|
515
562
|
clearTimeout(timeoutId);
|
|
516
563
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -560,13 +607,11 @@ function enumerateDevices() {
|
|
|
560
607
|
});
|
|
561
608
|
};
|
|
562
609
|
const timeoutId = setTimeout(() => {
|
|
563
|
-
checkDevices();
|
|
564
610
|
cleanup();
|
|
565
611
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
566
612
|
resolve(devices);
|
|
567
613
|
}, DEVICE_SCAN_TIMEOUT);
|
|
568
|
-
|
|
569
|
-
nobleInstance.startScanning([], false, (error) => {
|
|
614
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error) => {
|
|
570
615
|
if (error) {
|
|
571
616
|
cleanup();
|
|
572
617
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -661,7 +706,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
661
706
|
});
|
|
662
707
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
663
708
|
const services = yield new Promise((resolve, reject) => {
|
|
664
|
-
peripheral.discoverServices(
|
|
709
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
665
710
|
if (error) {
|
|
666
711
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
|
|
667
712
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -672,30 +717,12 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
672
717
|
});
|
|
673
718
|
});
|
|
674
719
|
if (!services || services.length === 0) {
|
|
675
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
676
|
-
}
|
|
677
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] services discovered', {
|
|
678
|
-
deviceId: peripheral.id,
|
|
679
|
-
serviceUUIDs: services.map(service => service.uuid),
|
|
680
|
-
});
|
|
681
|
-
let service = services.find(s => NORMALIZED_ONEKEY_SERVICE_UUIDS.has(getBleUuidKey(s.uuid)));
|
|
682
|
-
if (!service) {
|
|
683
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Known OneKey service UUID not found, trying first vendor service');
|
|
684
|
-
service =
|
|
685
|
-
services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
|
|
686
|
-
services.find(s => !isGenericBleService(s.uuid)) ||
|
|
687
|
-
services[0];
|
|
720
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
688
721
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
const selectedService = service;
|
|
693
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] service selected', {
|
|
694
|
-
deviceId: peripheral.id,
|
|
695
|
-
serviceUuid: selectedService.uuid,
|
|
696
|
-
});
|
|
722
|
+
const service = services[0];
|
|
723
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found service:', service.uuid);
|
|
697
724
|
const characteristics = yield new Promise((resolve, reject) => {
|
|
698
|
-
|
|
725
|
+
service.discoverCharacteristics([hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID, hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID], (error, chars) => {
|
|
699
726
|
if (error) {
|
|
700
727
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
701
728
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
@@ -705,6 +732,10 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
705
732
|
}
|
|
706
733
|
});
|
|
707
734
|
});
|
|
735
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered characteristics:', {
|
|
736
|
+
count: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.length) || 0,
|
|
737
|
+
uuids: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.map(c => c.uuid)) || [],
|
|
738
|
+
});
|
|
708
739
|
let writeCharacteristic = null;
|
|
709
740
|
let notifyCharacteristic = null;
|
|
710
741
|
for (const characteristic of characteristics) {
|
|
@@ -717,16 +748,14 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
717
748
|
notifyCharacteristic = characteristic;
|
|
718
749
|
}
|
|
719
750
|
}
|
|
751
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Characteristic discovery result:', {
|
|
752
|
+
writeFound: !!writeCharacteristic,
|
|
753
|
+
notifyFound: !!notifyCharacteristic,
|
|
754
|
+
});
|
|
720
755
|
if (!writeCharacteristic || !notifyCharacteristic) {
|
|
721
756
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Missing characteristics - write:', !!writeCharacteristic, 'notify:', !!notifyCharacteristic);
|
|
722
757
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, 'Required characteristics not found');
|
|
723
758
|
}
|
|
724
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] characteristics selected', {
|
|
725
|
-
deviceId: peripheral.id,
|
|
726
|
-
serviceUuid: selectedService.uuid,
|
|
727
|
-
writeUuid: writeCharacteristic.uuid,
|
|
728
|
-
notifyUuid: notifyCharacteristic.uuid,
|
|
729
|
-
});
|
|
730
759
|
return { write: writeCharacteristic, notify: notifyCharacteristic };
|
|
731
760
|
}))();
|
|
732
761
|
try {
|
|
@@ -915,6 +944,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
915
944
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
916
945
|
}
|
|
917
946
|
notificationCallbacks.delete(deviceId);
|
|
947
|
+
devicePacketStates.delete(deviceId);
|
|
918
948
|
subscribedDevices.delete(deviceId);
|
|
919
949
|
}
|
|
920
950
|
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
@@ -997,6 +1027,7 @@ function unsubscribeNotifications(deviceId) {
|
|
|
997
1027
|
});
|
|
998
1028
|
notifyCharacteristic.removeAllListeners('data');
|
|
999
1029
|
notificationCallbacks.delete(deviceId);
|
|
1030
|
+
devicePacketStates.delete(deviceId);
|
|
1000
1031
|
subscribedDevices.delete(deviceId);
|
|
1001
1032
|
}
|
|
1002
1033
|
finally {
|
|
@@ -1036,6 +1067,12 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1036
1067
|
if (subscribedDevices.get(deviceId)) {
|
|
1037
1068
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already subscribed to characteristic, updating callback only');
|
|
1038
1069
|
notificationCallbacks.set(deviceId, callback);
|
|
1070
|
+
devicePacketStates.set(deviceId, {
|
|
1071
|
+
bufferLength: 0,
|
|
1072
|
+
buffer: [],
|
|
1073
|
+
packetCount: 0,
|
|
1074
|
+
messageId: undefined,
|
|
1075
|
+
});
|
|
1039
1076
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1040
1077
|
return Promise.resolve();
|
|
1041
1078
|
}
|
|
@@ -1044,6 +1081,12 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1044
1081
|
}
|
|
1045
1082
|
notifyCharacteristic.removeAllListeners('data');
|
|
1046
1083
|
notificationCallbacks.set(deviceId, callback);
|
|
1084
|
+
devicePacketStates.set(deviceId, {
|
|
1085
|
+
bufferLength: 0,
|
|
1086
|
+
buffer: [],
|
|
1087
|
+
packetCount: 0,
|
|
1088
|
+
messageId: undefined,
|
|
1089
|
+
});
|
|
1047
1090
|
function rebuildAppSubscription(deviceId, notifyCharacteristic) {
|
|
1048
1091
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1049
1092
|
yield new Promise(resolve => {
|
|
@@ -1065,7 +1108,16 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1065
1108
|
pairedDevices.add(deviceId);
|
|
1066
1109
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1067
1110
|
}
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
1121
|
});
|
|
1070
1122
|
});
|
|
1071
1123
|
}
|
|
@@ -1079,17 +1131,19 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1079
1131
|
});
|
|
1080
1132
|
}
|
|
1081
1133
|
function setupNobleBleHandlers(webContents) {
|
|
1134
|
+
console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
|
|
1082
1135
|
try {
|
|
1136
|
+
console.log('[NobleBLE] NOBLE_VERSION_771');
|
|
1083
1137
|
logger = require('electron-log');
|
|
1138
|
+
console.log('[NobleBLE] electron-log loaded successfully.');
|
|
1084
1139
|
const { ipcMain } = require('electron');
|
|
1140
|
+
console.log('[NobleBLE] electron.ipcMain loaded successfully.');
|
|
1085
1141
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1142
|
+
console.log(`[NobleBLE] Registering handler for: ${hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
|
|
1086
1143
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1087
1144
|
try {
|
|
1088
1145
|
const devices = yield enumerateDevices();
|
|
1089
|
-
safeLog(logger, '
|
|
1090
|
-
count: devices.length,
|
|
1091
|
-
devices: devices.map(device => ({ id: device.id, name: device.name })),
|
|
1092
|
-
});
|
|
1146
|
+
safeLog(logger, 'info', 'Enumeration completed, devices:', devices);
|
|
1093
1147
|
return devices;
|
|
1094
1148
|
}
|
|
1095
1149
|
catch (error) {
|
|
@@ -1114,6 +1168,7 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1114
1168
|
yield disconnectDevice(deviceId);
|
|
1115
1169
|
}));
|
|
1116
1170
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1171
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
|
|
1117
1172
|
yield transmitHexDataToDevice(deviceId, hexData);
|
|
1118
1173
|
}));
|
|
1119
1174
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -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.34-alpha.
|
|
3
|
+
"version": "1.1.34-alpha.4",
|
|
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.34-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.1.34-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.1.34-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.1.34-alpha.4",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.1.34-alpha.4",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.1.34-alpha.4",
|
|
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": "d546bc55410990351d34009230aefea77725d0d6"
|
|
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,43 +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
|
-
]);
|
|
97
|
+
// Validation limits
|
|
98
|
+
const MIN_HEADER_LENGTH = 9; // Minimum header chunk length
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
// Packet processing result types
|
|
101
|
+
interface PacketProcessResult {
|
|
102
|
+
isComplete: boolean;
|
|
103
|
+
completePacket?: string;
|
|
104
|
+
error?: string;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(uuidKey)
|
|
106
|
-
);
|
|
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', {
|
|
111
|
+
deviceId,
|
|
112
|
+
dataLength: data.length,
|
|
107
113
|
});
|
|
108
|
-
}
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
184
|
}
|
|
114
185
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (appCb) appCb(data.toString('hex'));
|
|
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;
|
|
122
192
|
}
|
|
123
193
|
|
|
124
194
|
// Check Bluetooth availability - returns detailed state
|
|
@@ -180,6 +250,7 @@ function setupPersistentStateListener(): void {
|
|
|
180
250
|
deviceCharacteristics.clear();
|
|
181
251
|
subscribedDevices.clear();
|
|
182
252
|
notificationCallbacks.clear();
|
|
253
|
+
devicePacketStates.clear();
|
|
183
254
|
subscriptionOperations.clear();
|
|
184
255
|
pairedDevices.clear();
|
|
185
256
|
|
|
@@ -357,6 +428,7 @@ function cleanupDevice(
|
|
|
357
428
|
connectedDevices.delete(deviceId);
|
|
358
429
|
deviceCharacteristics.delete(deviceId);
|
|
359
430
|
notificationCallbacks.delete(deviceId);
|
|
431
|
+
devicePacketStates.delete(deviceId);
|
|
360
432
|
subscribedDevices.delete(deviceId);
|
|
361
433
|
subscriptionOperations.delete(deviceId);
|
|
362
434
|
pairedDevices.delete(deviceId);
|
|
@@ -518,7 +590,8 @@ async function attemptWindowsWriteUntilPaired(
|
|
|
518
590
|
subscriptionOperations,
|
|
519
591
|
subscribedDevices,
|
|
520
592
|
pairedDevices,
|
|
521
|
-
|
|
593
|
+
notificationCallbacks,
|
|
594
|
+
processNotificationData,
|
|
522
595
|
logger,
|
|
523
596
|
});
|
|
524
597
|
logger?.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -545,6 +618,12 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
545
618
|
}
|
|
546
619
|
|
|
547
620
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
621
|
+
logger?.info('[NobleBLE] Writing data:', {
|
|
622
|
+
deviceId,
|
|
623
|
+
dataLength: toBuffer.length,
|
|
624
|
+
firstBytes: toBuffer.subarray(0, 8).toString('hex'),
|
|
625
|
+
});
|
|
626
|
+
|
|
548
627
|
const doGetWriteCharacteristic = () => deviceCharacteristics.get(deviceId)?.write;
|
|
549
628
|
|
|
550
629
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
@@ -606,21 +685,15 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
606
685
|
|
|
607
686
|
// Handle discovered device (for general enumeration only)
|
|
608
687
|
function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
688
|
+
const deviceName = peripheral.advertisement?.localName || 'Unknown Device';
|
|
689
|
+
|
|
690
|
+
// Only process OneKey devices for general discovery
|
|
691
|
+
if (!isOnekeyDevice(deviceName)) {
|
|
612
692
|
return;
|
|
613
693
|
}
|
|
614
694
|
|
|
615
|
-
|
|
695
|
+
logger?.info('[NobleBLE] Discovered OneKey device:', deviceName);
|
|
616
696
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
617
|
-
if (isNewDevice) {
|
|
618
|
-
logger?.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
619
|
-
deviceId: peripheral.id,
|
|
620
|
-
name: peripheral.advertisement?.localName || 'Unknown Device',
|
|
621
|
-
serviceUUIDs: peripheral.advertisement?.serviceUuids || [],
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
697
|
}
|
|
625
698
|
|
|
626
699
|
// Ensure discover listener is properly set up
|
|
@@ -680,8 +753,8 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
|
|
|
680
753
|
// Add local listener for this scan
|
|
681
754
|
nobleInstance.on('discover', onDiscover);
|
|
682
755
|
|
|
683
|
-
// Start scanning
|
|
684
|
-
nobleInstance.startScanning(
|
|
756
|
+
// Start scanning
|
|
757
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
|
|
685
758
|
if (error) {
|
|
686
759
|
clearTimeout(timeoutId);
|
|
687
760
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -743,19 +816,15 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
|
|
|
743
816
|
});
|
|
744
817
|
};
|
|
745
818
|
|
|
746
|
-
// Set timeout for scanning
|
|
819
|
+
// Set timeout for scanning
|
|
747
820
|
const timeoutId = setTimeout(() => {
|
|
748
|
-
// Final collection before resolving — catches devices discovered near the deadline
|
|
749
|
-
checkDevices();
|
|
750
821
|
cleanup();
|
|
751
822
|
logger?.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
752
823
|
resolve(devices);
|
|
753
824
|
}, DEVICE_SCAN_TIMEOUT);
|
|
754
825
|
|
|
755
|
-
// Start scanning
|
|
756
|
-
|
|
757
|
-
logger?.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
758
|
-
nobleInstance.startScanning([], false, (error?: Error) => {
|
|
826
|
+
// Start scanning for OneKey service UUIDs
|
|
827
|
+
nobleInstance.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
|
|
759
828
|
if (error) {
|
|
760
829
|
cleanup();
|
|
761
830
|
logger?.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -883,9 +952,9 @@ async function discoverServicesAndCharacteristics(
|
|
|
883
952
|
|
|
884
953
|
// Main discovery logic as async function
|
|
885
954
|
const discoveryPromise = (async (): Promise<CharacteristicPair> => {
|
|
886
|
-
// Step 1: Discover
|
|
955
|
+
// Step 1: Discover services (promisified)
|
|
887
956
|
const services = await new Promise<Service[]>((resolve, reject) => {
|
|
888
|
-
peripheral.discoverServices(
|
|
957
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
889
958
|
if (error) {
|
|
890
959
|
logger?.error('[NobleBLE] Service discovery failed:', error);
|
|
891
960
|
reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -896,44 +965,33 @@ async function discoverServicesAndCharacteristics(
|
|
|
896
965
|
});
|
|
897
966
|
|
|
898
967
|
if (!services || services.length === 0) {
|
|
899
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
968
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
900
969
|
}
|
|
901
970
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
serviceUUIDs: services.map(service => service.uuid),
|
|
905
|
-
});
|
|
971
|
+
const service = services[0];
|
|
972
|
+
logger?.info('[NobleBLE] Found service:', service.uuid);
|
|
906
973
|
|
|
907
|
-
//
|
|
908
|
-
let service = services.find(s => NORMALIZED_ONEKEY_SERVICE_UUIDS.has(getBleUuidKey(s.uuid)));
|
|
909
|
-
if (!service) {
|
|
910
|
-
logger?.info('[NobleBLE] Known OneKey service UUID not found, trying first vendor service');
|
|
911
|
-
service =
|
|
912
|
-
services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
|
|
913
|
-
services.find(s => !isGenericBleService(s.uuid)) ||
|
|
914
|
-
services[0];
|
|
915
|
-
}
|
|
916
|
-
if (!service) {
|
|
917
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
918
|
-
}
|
|
919
|
-
const selectedService = service;
|
|
920
|
-
logger?.debug('[NobleBLE] service selected', {
|
|
921
|
-
deviceId: peripheral.id,
|
|
922
|
-
serviceUuid: selectedService.uuid,
|
|
923
|
-
});
|
|
924
|
-
// Step 2: Discover ALL characteristics (no filter)
|
|
974
|
+
// Step 2: Discover characteristics (promisified)
|
|
925
975
|
const characteristics = await new Promise<Characteristic[]>((resolve, reject) => {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
+
}
|
|
932
985
|
}
|
|
933
|
-
|
|
986
|
+
);
|
|
934
987
|
});
|
|
935
988
|
|
|
936
989
|
// Step 3: Find required characteristics
|
|
990
|
+
logger?.info('[NobleBLE] Discovered characteristics:', {
|
|
991
|
+
count: characteristics?.length || 0,
|
|
992
|
+
uuids: characteristics?.map(c => c.uuid) || [],
|
|
993
|
+
});
|
|
994
|
+
|
|
937
995
|
let writeCharacteristic: Characteristic | null = null;
|
|
938
996
|
let notifyCharacteristic: Characteristic | null = null;
|
|
939
997
|
|
|
@@ -948,6 +1006,11 @@ async function discoverServicesAndCharacteristics(
|
|
|
948
1006
|
}
|
|
949
1007
|
}
|
|
950
1008
|
|
|
1009
|
+
logger?.info('[NobleBLE] Characteristic discovery result:', {
|
|
1010
|
+
writeFound: !!writeCharacteristic,
|
|
1011
|
+
notifyFound: !!notifyCharacteristic,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
951
1014
|
if (!writeCharacteristic || !notifyCharacteristic) {
|
|
952
1015
|
logger?.error(
|
|
953
1016
|
'[NobleBLE] Missing characteristics - write:',
|
|
@@ -961,13 +1024,6 @@ async function discoverServicesAndCharacteristics(
|
|
|
961
1024
|
);
|
|
962
1025
|
}
|
|
963
1026
|
|
|
964
|
-
logger?.debug('[NobleBLE] characteristics selected', {
|
|
965
|
-
deviceId: peripheral.id,
|
|
966
|
-
serviceUuid: selectedService.uuid,
|
|
967
|
-
writeUuid: writeCharacteristic.uuid,
|
|
968
|
-
notifyUuid: notifyCharacteristic.uuid,
|
|
969
|
-
});
|
|
970
|
-
|
|
971
1027
|
return { write: writeCharacteristic, notify: notifyCharacteristic };
|
|
972
1028
|
})();
|
|
973
1029
|
|
|
@@ -1264,6 +1320,7 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1264
1320
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
1265
1321
|
}
|
|
1266
1322
|
notificationCallbacks.delete(deviceId);
|
|
1323
|
+
devicePacketStates.delete(deviceId);
|
|
1267
1324
|
subscribedDevices.delete(deviceId);
|
|
1268
1325
|
// Continue to re-setup the connection properly
|
|
1269
1326
|
}
|
|
@@ -1374,6 +1431,7 @@ async function unsubscribeNotifications(deviceId: string): Promise<void> {
|
|
|
1374
1431
|
// Remove all listeners and clear subscription status
|
|
1375
1432
|
notifyCharacteristic.removeAllListeners('data');
|
|
1376
1433
|
notificationCallbacks.delete(deviceId);
|
|
1434
|
+
devicePacketStates.delete(deviceId);
|
|
1377
1435
|
subscribedDevices.delete(deviceId);
|
|
1378
1436
|
} finally {
|
|
1379
1437
|
// 🔒 CRITICAL: Always clear operation state (even on error)
|
|
@@ -1440,6 +1498,14 @@ async function subscribeNotifications(
|
|
|
1440
1498
|
// Just update the callback without re-subscribing
|
|
1441
1499
|
notificationCallbacks.set(deviceId, callback);
|
|
1442
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
|
+
|
|
1443
1509
|
// 🔒 Clear operation state
|
|
1444
1510
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1445
1511
|
return Promise.resolve();
|
|
@@ -1456,6 +1522,14 @@ async function subscribeNotifications(
|
|
|
1456
1522
|
// Store callback for this device
|
|
1457
1523
|
notificationCallbacks.set(deviceId, callback);
|
|
1458
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
|
+
|
|
1459
1533
|
// Helper: rebuild a clean application-layer subscription
|
|
1460
1534
|
async function rebuildAppSubscription(
|
|
1461
1535
|
deviceId: string,
|
|
@@ -1484,7 +1558,15 @@ async function subscribeNotifications(
|
|
|
1484
1558
|
logger?.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1485
1559
|
}
|
|
1486
1560
|
|
|
1487
|
-
|
|
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
|
+
}
|
|
1488
1570
|
});
|
|
1489
1571
|
}
|
|
1490
1572
|
|
|
@@ -1499,25 +1581,29 @@ async function subscribeNotifications(
|
|
|
1499
1581
|
|
|
1500
1582
|
// Setup IPC handlers
|
|
1501
1583
|
export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
1584
|
+
// Use console.log for initial logging as electron-log might not be available yet.
|
|
1585
|
+
console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
|
|
1502
1586
|
try {
|
|
1587
|
+
console.log('[NobleBLE] NOBLE_VERSION_771');
|
|
1588
|
+
|
|
1503
1589
|
// @ts-ignore – electron-log is only available at runtime
|
|
1504
1590
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
1505
1591
|
logger = require('electron-log') as Logger;
|
|
1592
|
+
console.log('[NobleBLE] electron-log loaded successfully.');
|
|
1506
1593
|
|
|
1507
1594
|
// @ts-ignore – electron is only available at runtime
|
|
1508
1595
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
1509
1596
|
const { ipcMain } = require('electron');
|
|
1597
|
+
console.log('[NobleBLE] electron.ipcMain loaded successfully.');
|
|
1510
1598
|
|
|
1511
1599
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1512
1600
|
|
|
1513
1601
|
// Handle enumerate request
|
|
1602
|
+
console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
|
|
1514
1603
|
ipcMain.handle(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, async () => {
|
|
1515
1604
|
try {
|
|
1516
1605
|
const devices = await enumerateDevices();
|
|
1517
|
-
safeLog(logger, '
|
|
1518
|
-
count: devices.length,
|
|
1519
|
-
devices: devices.map(device => ({ id: device.id, name: device.name })),
|
|
1520
|
-
});
|
|
1606
|
+
safeLog(logger, 'info', 'Enumeration completed, devices:', devices);
|
|
1521
1607
|
return devices;
|
|
1522
1608
|
} catch (error) {
|
|
1523
1609
|
safeLog(logger, 'error', 'Enumeration failed:', error);
|
|
@@ -1562,6 +1648,7 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1562
1648
|
ipcMain.handle(
|
|
1563
1649
|
EOneKeyBleMessageKeys.NOBLE_BLE_WRITE,
|
|
1564
1650
|
async (_event: IpcMainInvokeEvent, deviceId: string, hexData: string) => {
|
|
1651
|
+
logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
|
|
1565
1652
|
await transmitHexDataToDevice(deviceId, hexData);
|
|
1566
1653
|
}
|
|
1567
1654
|
);
|