@onekeyfe/hd-transport-electron 1.1.34-alpha.1 → 1.1.34-alpha.3
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 +1 -6
- package/dist/ble-ops.d.ts.map +1 -1
- package/dist/{index-de36348d.js → index-55699e30.js} +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-2e41f27c.js → noble-ble-handler-16791965.js} +83 -138
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/ble-ops.ts +3 -20
- package/src/noble-ble-handler.ts +99 -186
package/dist/ble-ops.d.ts
CHANGED
|
@@ -7,12 +7,7 @@ export interface SoftRefreshParams {
|
|
|
7
7
|
subscriptionOperations: Map<string, 'subscribing' | 'unsubscribing' | 'idle'>;
|
|
8
8
|
subscribedDevices: Map<string, boolean>;
|
|
9
9
|
pairedDevices: Set<string>;
|
|
10
|
-
|
|
11
|
-
processNotificationData: (deviceId: string, data: Buffer) => {
|
|
12
|
-
isComplete: boolean;
|
|
13
|
-
completePacket?: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
};
|
|
10
|
+
onNotificationData: (deviceId: string, data: Buffer) => void;
|
|
16
11
|
logger: Logger | null;
|
|
17
12
|
}
|
|
18
13
|
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,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CtF"}
|
|
@@ -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-16791965.js'); });
|
|
36
36
|
setupNobleBleHandlers(webContents);
|
|
37
37
|
});
|
|
38
38
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ 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
|
+
*/
|
|
29
33
|
interface NobleBleAPI {
|
|
30
34
|
enumerate: () => Promise<{
|
|
31
35
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-55699e30.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
|
-
var hdTransport = require('@onekeyfe/hd-transport');
|
|
6
5
|
var pRetry = require('p-retry');
|
|
7
6
|
|
|
8
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -20,7 +19,7 @@ function safeLog(logger, level, message, ...args) {
|
|
|
20
19
|
|
|
21
20
|
function softRefreshSubscription(params) {
|
|
22
21
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices,
|
|
22
|
+
const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices, onNotificationData, logger, } = params;
|
|
24
23
|
if (!notifyCharacteristic) {
|
|
25
24
|
throw new Error(`Notify characteristic not available for device ${deviceId}`);
|
|
26
25
|
}
|
|
@@ -44,16 +43,7 @@ function softRefreshSubscription(params) {
|
|
|
44
43
|
pairedDevices.add(deviceId);
|
|
45
44
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Device paired successfully', { deviceId });
|
|
46
45
|
}
|
|
47
|
-
|
|
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
|
-
}
|
|
46
|
+
onNotificationData(deviceId, data);
|
|
57
47
|
});
|
|
58
48
|
subscribedDevices.set(deviceId, true);
|
|
59
49
|
subscriptionOperations.set(deviceId, 'idle');
|
|
@@ -76,15 +66,15 @@ const deviceCharacteristics = new Map();
|
|
|
76
66
|
const notificationCallbacks = new Map();
|
|
77
67
|
const subscribedDevices = new Map();
|
|
78
68
|
const subscriptionOperations = new Map();
|
|
79
|
-
const devicePacketStates = new Map();
|
|
80
69
|
const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
|
|
70
|
+
const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
|
|
81
71
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
82
72
|
const NORMALIZED_NOTIFY_UUID = '0003';
|
|
83
73
|
const BLUETOOTH_INIT_TIMEOUT = 10000;
|
|
84
|
-
const DEVICE_SCAN_TIMEOUT =
|
|
85
|
-
const FAST_SCAN_TIMEOUT =
|
|
74
|
+
const DEVICE_SCAN_TIMEOUT = 8000;
|
|
75
|
+
const FAST_SCAN_TIMEOUT = 8000;
|
|
86
76
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
87
|
-
const CONNECTION_TIMEOUT =
|
|
77
|
+
const CONNECTION_TIMEOUT = 8000;
|
|
88
78
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
89
79
|
const BLE_PACKET_SIZE = 192;
|
|
90
80
|
const UNIFIED_WRITE_DELAY = 5;
|
|
@@ -93,70 +83,35 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
93
83
|
const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
94
84
|
/status:\s*3/i,
|
|
95
85
|
];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
function getBleUuidKey(uuid) {
|
|
87
|
+
const normalized = (uuid !== null && uuid !== void 0 ? uuid : '').replace(/-/g, '').toLowerCase();
|
|
88
|
+
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
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));
|
|
101
104
|
});
|
|
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
105
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
113
|
+
if (appCb)
|
|
114
|
+
appCb(data.toString('hex'));
|
|
160
115
|
}
|
|
161
116
|
function checkBluetoothAvailability() {
|
|
162
117
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -201,7 +156,6 @@ function setupPersistentStateListener() {
|
|
|
201
156
|
deviceCharacteristics.clear();
|
|
202
157
|
subscribedDevices.clear();
|
|
203
158
|
notificationCallbacks.clear();
|
|
204
|
-
devicePacketStates.clear();
|
|
205
159
|
subscriptionOperations.clear();
|
|
206
160
|
pairedDevices.clear();
|
|
207
161
|
if (noble) {
|
|
@@ -321,7 +275,6 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
321
275
|
connectedDevices.delete(deviceId);
|
|
322
276
|
deviceCharacteristics.delete(deviceId);
|
|
323
277
|
notificationCallbacks.delete(deviceId);
|
|
324
|
-
devicePacketStates.delete(deviceId);
|
|
325
278
|
subscribedDevices.delete(deviceId);
|
|
326
279
|
subscriptionOperations.delete(deviceId);
|
|
327
280
|
pairedDevices.delete(deviceId);
|
|
@@ -436,8 +389,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
436
389
|
subscriptionOperations,
|
|
437
390
|
subscribedDevices,
|
|
438
391
|
pairedDevices,
|
|
439
|
-
|
|
440
|
-
processNotificationData,
|
|
392
|
+
onNotificationData: emitRawNotification,
|
|
441
393
|
logger,
|
|
442
394
|
});
|
|
443
395
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -458,11 +410,6 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
458
410
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
|
|
459
411
|
}
|
|
460
412
|
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
|
-
});
|
|
466
413
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
467
414
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
468
415
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
@@ -506,13 +453,19 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
506
453
|
});
|
|
507
454
|
}
|
|
508
455
|
function handleDeviceDiscovered(peripheral) {
|
|
509
|
-
var _a;
|
|
510
|
-
|
|
511
|
-
if (!hdShared.isOnekeyDevice(deviceName)) {
|
|
456
|
+
var _a, _b;
|
|
457
|
+
if (!isOneKeyPeripheral(peripheral)) {
|
|
512
458
|
return;
|
|
513
459
|
}
|
|
514
|
-
|
|
460
|
+
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
515
461
|
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
|
+
}
|
|
516
469
|
}
|
|
517
470
|
function ensureDiscoverListener() {
|
|
518
471
|
if (!noble)
|
|
@@ -557,7 +510,7 @@ function performTargetedScan(targetDeviceId) {
|
|
|
557
510
|
resolve(null);
|
|
558
511
|
}, FAST_SCAN_TIMEOUT);
|
|
559
512
|
nobleInstance.on('discover', onDiscover);
|
|
560
|
-
nobleInstance.startScanning(
|
|
513
|
+
nobleInstance.startScanning([], false, (error) => {
|
|
561
514
|
if (error) {
|
|
562
515
|
clearTimeout(timeoutId);
|
|
563
516
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -607,11 +560,13 @@ function enumerateDevices() {
|
|
|
607
560
|
});
|
|
608
561
|
};
|
|
609
562
|
const timeoutId = setTimeout(() => {
|
|
563
|
+
checkDevices();
|
|
610
564
|
cleanup();
|
|
611
565
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
612
566
|
resolve(devices);
|
|
613
567
|
}, DEVICE_SCAN_TIMEOUT);
|
|
614
|
-
|
|
568
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
569
|
+
nobleInstance.startScanning([], false, (error) => {
|
|
615
570
|
if (error) {
|
|
616
571
|
cleanup();
|
|
617
572
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -706,7 +661,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
706
661
|
});
|
|
707
662
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
708
663
|
const services = yield new Promise((resolve, reject) => {
|
|
709
|
-
peripheral.discoverServices(
|
|
664
|
+
peripheral.discoverServices([], (error, svc) => {
|
|
710
665
|
if (error) {
|
|
711
666
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
|
|
712
667
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -717,12 +672,30 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
717
672
|
});
|
|
718
673
|
});
|
|
719
674
|
if (!services || services.length === 0) {
|
|
720
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No
|
|
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];
|
|
721
688
|
}
|
|
722
|
-
|
|
723
|
-
|
|
689
|
+
if (!service) {
|
|
690
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
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
|
+
});
|
|
724
697
|
const characteristics = yield new Promise((resolve, reject) => {
|
|
725
|
-
|
|
698
|
+
selectedService.discoverCharacteristics([], (error, chars) => {
|
|
726
699
|
if (error) {
|
|
727
700
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
728
701
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
@@ -732,10 +705,6 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
732
705
|
}
|
|
733
706
|
});
|
|
734
707
|
});
|
|
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
|
-
});
|
|
739
708
|
let writeCharacteristic = null;
|
|
740
709
|
let notifyCharacteristic = null;
|
|
741
710
|
for (const characteristic of characteristics) {
|
|
@@ -748,14 +717,16 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
748
717
|
notifyCharacteristic = characteristic;
|
|
749
718
|
}
|
|
750
719
|
}
|
|
751
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Characteristic discovery result:', {
|
|
752
|
-
writeFound: !!writeCharacteristic,
|
|
753
|
-
notifyFound: !!notifyCharacteristic,
|
|
754
|
-
});
|
|
755
720
|
if (!writeCharacteristic || !notifyCharacteristic) {
|
|
756
721
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Missing characteristics - write:', !!writeCharacteristic, 'notify:', !!notifyCharacteristic);
|
|
757
722
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, 'Required characteristics not found');
|
|
758
723
|
}
|
|
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
|
+
});
|
|
759
730
|
return { write: writeCharacteristic, notify: notifyCharacteristic };
|
|
760
731
|
}))();
|
|
761
732
|
try {
|
|
@@ -944,7 +915,6 @@ function connectDevice(deviceId, webContents) {
|
|
|
944
915
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
945
916
|
}
|
|
946
917
|
notificationCallbacks.delete(deviceId);
|
|
947
|
-
devicePacketStates.delete(deviceId);
|
|
948
918
|
subscribedDevices.delete(deviceId);
|
|
949
919
|
}
|
|
950
920
|
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
@@ -1027,7 +997,6 @@ function unsubscribeNotifications(deviceId) {
|
|
|
1027
997
|
});
|
|
1028
998
|
notifyCharacteristic.removeAllListeners('data');
|
|
1029
999
|
notificationCallbacks.delete(deviceId);
|
|
1030
|
-
devicePacketStates.delete(deviceId);
|
|
1031
1000
|
subscribedDevices.delete(deviceId);
|
|
1032
1001
|
}
|
|
1033
1002
|
finally {
|
|
@@ -1067,12 +1036,6 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1067
1036
|
if (subscribedDevices.get(deviceId)) {
|
|
1068
1037
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already subscribed to characteristic, updating callback only');
|
|
1069
1038
|
notificationCallbacks.set(deviceId, callback);
|
|
1070
|
-
devicePacketStates.set(deviceId, {
|
|
1071
|
-
bufferLength: 0,
|
|
1072
|
-
buffer: [],
|
|
1073
|
-
packetCount: 0,
|
|
1074
|
-
messageId: undefined,
|
|
1075
|
-
});
|
|
1076
1039
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1077
1040
|
return Promise.resolve();
|
|
1078
1041
|
}
|
|
@@ -1081,12 +1044,6 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1081
1044
|
}
|
|
1082
1045
|
notifyCharacteristic.removeAllListeners('data');
|
|
1083
1046
|
notificationCallbacks.set(deviceId, callback);
|
|
1084
|
-
devicePacketStates.set(deviceId, {
|
|
1085
|
-
bufferLength: 0,
|
|
1086
|
-
buffer: [],
|
|
1087
|
-
packetCount: 0,
|
|
1088
|
-
messageId: undefined,
|
|
1089
|
-
});
|
|
1090
1047
|
function rebuildAppSubscription(deviceId, notifyCharacteristic) {
|
|
1091
1048
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1092
1049
|
yield new Promise(resolve => {
|
|
@@ -1108,16 +1065,7 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1108
1065
|
pairedDevices.add(deviceId);
|
|
1109
1066
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1110
1067
|
}
|
|
1111
|
-
|
|
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
|
-
}
|
|
1068
|
+
emitRawNotification(deviceId, data);
|
|
1121
1069
|
});
|
|
1122
1070
|
});
|
|
1123
1071
|
}
|
|
@@ -1131,19 +1079,17 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1131
1079
|
});
|
|
1132
1080
|
}
|
|
1133
1081
|
function setupNobleBleHandlers(webContents) {
|
|
1134
|
-
console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
|
|
1135
1082
|
try {
|
|
1136
|
-
console.log('[NobleBLE] NOBLE_VERSION_771');
|
|
1137
1083
|
logger = require('electron-log');
|
|
1138
|
-
console.log('[NobleBLE] electron-log loaded successfully.');
|
|
1139
1084
|
const { ipcMain } = require('electron');
|
|
1140
|
-
console.log('[NobleBLE] electron.ipcMain loaded successfully.');
|
|
1141
1085
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1142
|
-
console.log(`[NobleBLE] Registering handler for: ${hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
|
|
1143
1086
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1144
1087
|
try {
|
|
1145
1088
|
const devices = yield enumerateDevices();
|
|
1146
|
-
safeLog(logger, '
|
|
1089
|
+
safeLog(logger, 'debug', 'Enumeration completed', {
|
|
1090
|
+
count: devices.length,
|
|
1091
|
+
devices: devices.map(device => ({ id: device.id, name: device.name })),
|
|
1092
|
+
});
|
|
1147
1093
|
return devices;
|
|
1148
1094
|
}
|
|
1149
1095
|
catch (error) {
|
|
@@ -1168,7 +1114,6 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1168
1114
|
yield disconnectDevice(deviceId);
|
|
1169
1115
|
}));
|
|
1170
1116
|
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 });
|
|
1172
1117
|
yield transmitHexDataToDevice(deviceId, hexData);
|
|
1173
1118
|
}));
|
|
1174
1119
|
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":"AAoBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAw8ChE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAiKpE"}
|
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.3",
|
|
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.3",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.1.34-alpha.3",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.1.34-alpha.3",
|
|
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": "3688bc8495eeb2ffd5616270d13fa29e8967623a"
|
|
40
40
|
}
|
package/src/ble-ops.ts
CHANGED
|
@@ -7,15 +7,7 @@ export interface SoftRefreshParams {
|
|
|
7
7
|
subscriptionOperations: Map<string, 'subscribing' | 'unsubscribing' | 'idle'>;
|
|
8
8
|
subscribedDevices: Map<string, boolean>;
|
|
9
9
|
pairedDevices: Set<string>;
|
|
10
|
-
|
|
11
|
-
processNotificationData: (
|
|
12
|
-
deviceId: string,
|
|
13
|
-
data: Buffer
|
|
14
|
-
) => {
|
|
15
|
-
isComplete: boolean;
|
|
16
|
-
completePacket?: string;
|
|
17
|
-
error?: string;
|
|
18
|
-
};
|
|
10
|
+
onNotificationData: (deviceId: string, data: Buffer) => void;
|
|
19
11
|
logger: Logger | null;
|
|
20
12
|
}
|
|
21
13
|
|
|
@@ -26,8 +18,7 @@ export async function softRefreshSubscription(params: SoftRefreshParams): Promis
|
|
|
26
18
|
subscriptionOperations,
|
|
27
19
|
subscribedDevices,
|
|
28
20
|
pairedDevices,
|
|
29
|
-
|
|
30
|
-
processNotificationData,
|
|
21
|
+
onNotificationData,
|
|
31
22
|
logger,
|
|
32
23
|
} = params;
|
|
33
24
|
|
|
@@ -60,15 +51,7 @@ export async function softRefreshSubscription(params: SoftRefreshParams): Promis
|
|
|
60
51
|
logger?.info('[BLE-OPS] Device paired successfully', { deviceId });
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
|
|
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
|
-
}
|
|
54
|
+
onNotificationData(deviceId, data);
|
|
72
55
|
});
|
|
73
56
|
|
|
74
57
|
subscribedDevices.set(deviceId, true);
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -9,14 +9,10 @@ import {
|
|
|
9
9
|
EOneKeyBleMessageKeys,
|
|
10
10
|
ERRORS,
|
|
11
11
|
HardwareErrorCode,
|
|
12
|
-
ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
|
|
13
12
|
ONEKEY_SERVICE_UUID,
|
|
14
|
-
ONEKEY_WRITE_CHARACTERISTIC_UUID,
|
|
15
|
-
isHeaderChunk,
|
|
16
13
|
isOnekeyDevice,
|
|
17
14
|
wait,
|
|
18
15
|
} from '@onekeyfe/hd-shared';
|
|
19
|
-
import { COMMON_HEADER_SIZE } from '@onekeyfe/hd-transport';
|
|
20
16
|
import pRetry from 'p-retry';
|
|
21
17
|
|
|
22
18
|
import { safeLog } from './types/noble-extended';
|
|
@@ -55,15 +51,6 @@ const subscribedDevices = new Map<string, boolean>(); // Track subscription stat
|
|
|
55
51
|
// 🔒 Subscription operation state tracking to prevent race conditions
|
|
56
52
|
const subscriptionOperations = new Map<string, 'subscribing' | 'unsubscribing' | 'idle'>();
|
|
57
53
|
|
|
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
|
-
|
|
67
54
|
// Windows-only response watchdog state moved to utils/windows-ble-recovery
|
|
68
55
|
|
|
69
56
|
// Pairing-related state removed
|
|
@@ -72,6 +59,7 @@ const devicePacketStates = new Map<string, PacketAssemblyState>();
|
|
|
72
59
|
|
|
73
60
|
// Service UUIDs to scan for - using constants from hd-shared
|
|
74
61
|
const ONEKEY_SERVICE_UUIDS = [ONEKEY_SERVICE_UUID];
|
|
62
|
+
const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
|
|
75
63
|
|
|
76
64
|
// Pre-normalized characteristic identifiers for fast comparison
|
|
77
65
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
@@ -79,10 +67,10 @@ const NORMALIZED_NOTIFY_UUID = '0003';
|
|
|
79
67
|
|
|
80
68
|
// Timeout and interval constants
|
|
81
69
|
const BLUETOOTH_INIT_TIMEOUT = 10000; // 10 seconds for Bluetooth initialization
|
|
82
|
-
const DEVICE_SCAN_TIMEOUT =
|
|
83
|
-
const FAST_SCAN_TIMEOUT =
|
|
70
|
+
const DEVICE_SCAN_TIMEOUT = 8000; // 8 seconds for device scanning (Pro2 has longer advertising interval)
|
|
71
|
+
const FAST_SCAN_TIMEOUT = 8000; // 8 seconds for targeted scanning (Pro2 has longer advertising interval)
|
|
84
72
|
const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
|
|
85
|
-
const CONNECTION_TIMEOUT =
|
|
73
|
+
const CONNECTION_TIMEOUT = 8000; // 8 seconds for device connection (BLE reconnect after release can be slow)
|
|
86
74
|
const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
|
|
87
75
|
|
|
88
76
|
// Write-related constants
|
|
@@ -94,101 +82,43 @@ const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
|
94
82
|
/status:\s*3/i, // Windows pairing cancelled / GATT write failed
|
|
95
83
|
];
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
// Packet processing result types
|
|
101
|
-
interface PacketProcessResult {
|
|
102
|
-
isComplete: boolean;
|
|
103
|
-
completePacket?: string;
|
|
104
|
-
error?: string;
|
|
85
|
+
function getBleUuidKey(uuid?: string | null) {
|
|
86
|
+
const normalized = (uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
87
|
+
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
105
88
|
}
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
deviceId,
|
|
112
|
-
dataLength: data.length,
|
|
113
|
-
});
|
|
114
|
-
|
|
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
|
-
}
|
|
90
|
+
const NORMALIZED_ONEKEY_SERVICE_UUIDS = new Set([
|
|
91
|
+
...ONEKEY_SERVICE_UUIDS.map(uuid => getBleUuidKey(uuid)),
|
|
92
|
+
'0001',
|
|
93
|
+
]);
|
|
122
94
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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);
|
|
95
|
+
function isGenericBleService(uuid?: string | null) {
|
|
96
|
+
return ['1800', '1801', '180a', '180f'].includes(getBleUuidKey(uuid));
|
|
97
|
+
}
|
|
175
98
|
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
109
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return { isComplete: false, error: `Packet processing error: ${error}` };
|
|
183
|
-
}
|
|
110
|
+
function isOneKeyPeripheral(peripheral: Peripheral) {
|
|
111
|
+
const deviceName = peripheral.advertisement?.localName || null;
|
|
112
|
+
return isOnekeyDevice(deviceName, peripheral.id) || hasOneKeyAdvertisementService(peripheral);
|
|
184
113
|
}
|
|
185
114
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Forward a single BLE notification chunk (not an assembled packet) to the
|
|
117
|
+
* renderer-side transport. Packet reassembly is handled by ElectronBleTransport.
|
|
118
|
+
*/
|
|
119
|
+
function emitRawNotification(deviceId: string, data: Buffer): void {
|
|
120
|
+
const appCb = notificationCallbacks.get(deviceId);
|
|
121
|
+
if (appCb) appCb(data.toString('hex'));
|
|
192
122
|
}
|
|
193
123
|
|
|
194
124
|
// Check Bluetooth availability - returns detailed state
|
|
@@ -250,7 +180,6 @@ function setupPersistentStateListener(): void {
|
|
|
250
180
|
deviceCharacteristics.clear();
|
|
251
181
|
subscribedDevices.clear();
|
|
252
182
|
notificationCallbacks.clear();
|
|
253
|
-
devicePacketStates.clear();
|
|
254
183
|
subscriptionOperations.clear();
|
|
255
184
|
pairedDevices.clear();
|
|
256
185
|
|
|
@@ -428,7 +357,6 @@ function cleanupDevice(
|
|
|
428
357
|
connectedDevices.delete(deviceId);
|
|
429
358
|
deviceCharacteristics.delete(deviceId);
|
|
430
359
|
notificationCallbacks.delete(deviceId);
|
|
431
|
-
devicePacketStates.delete(deviceId);
|
|
432
360
|
subscribedDevices.delete(deviceId);
|
|
433
361
|
subscriptionOperations.delete(deviceId);
|
|
434
362
|
pairedDevices.delete(deviceId);
|
|
@@ -590,8 +518,7 @@ async function attemptWindowsWriteUntilPaired(
|
|
|
590
518
|
subscriptionOperations,
|
|
591
519
|
subscribedDevices,
|
|
592
520
|
pairedDevices,
|
|
593
|
-
|
|
594
|
-
processNotificationData,
|
|
521
|
+
onNotificationData: emitRawNotification,
|
|
595
522
|
logger,
|
|
596
523
|
});
|
|
597
524
|
logger?.info('[BLE-Write] Subscription refresh completed', { deviceId });
|
|
@@ -618,12 +545,6 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
618
545
|
}
|
|
619
546
|
|
|
620
547
|
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
|
-
|
|
627
548
|
const doGetWriteCharacteristic = () => deviceCharacteristics.get(deviceId)?.write;
|
|
628
549
|
|
|
629
550
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
@@ -685,15 +606,21 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
685
606
|
|
|
686
607
|
// Handle discovered device (for general enumeration only)
|
|
687
608
|
function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (!isOnekeyDevice(deviceName)) {
|
|
609
|
+
// Only process OneKey candidates for general discovery. Avoid logging every
|
|
610
|
+
// ambient BLE peripheral; it makes Pro2 debugging hard to read.
|
|
611
|
+
if (!isOneKeyPeripheral(peripheral)) {
|
|
692
612
|
return;
|
|
693
613
|
}
|
|
694
614
|
|
|
695
|
-
|
|
615
|
+
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
696
616
|
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
|
+
}
|
|
697
624
|
}
|
|
698
625
|
|
|
699
626
|
// Ensure discover listener is properly set up
|
|
@@ -753,8 +680,8 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
|
|
|
753
680
|
// Add local listener for this scan
|
|
754
681
|
nobleInstance.on('discover', onDiscover);
|
|
755
682
|
|
|
756
|
-
// Start scanning
|
|
757
|
-
nobleInstance.startScanning(
|
|
683
|
+
// Start scanning — no service UUID filter (Pro2 may use different service UUID)
|
|
684
|
+
nobleInstance.startScanning([], false, (error?: Error) => {
|
|
758
685
|
if (error) {
|
|
759
686
|
clearTimeout(timeoutId);
|
|
760
687
|
nobleInstance.removeListener('discover', onDiscover);
|
|
@@ -816,15 +743,19 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
|
|
|
816
743
|
});
|
|
817
744
|
};
|
|
818
745
|
|
|
819
|
-
// Set timeout for scanning
|
|
746
|
+
// Set timeout for scanning — use longer timeout to catch slow-advertising devices like Pro2
|
|
820
747
|
const timeoutId = setTimeout(() => {
|
|
748
|
+
// Final collection before resolving — catches devices discovered near the deadline
|
|
749
|
+
checkDevices();
|
|
821
750
|
cleanup();
|
|
822
751
|
logger?.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
823
752
|
resolve(devices);
|
|
824
753
|
}, DEVICE_SCAN_TIMEOUT);
|
|
825
754
|
|
|
826
|
-
// Start scanning
|
|
827
|
-
|
|
755
|
+
// Start scanning without a service UUID filter so Pro2 advertisements with
|
|
756
|
+
// short vendor UUIDs can be found, but only OneKey candidates are logged/returned.
|
|
757
|
+
logger?.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
758
|
+
nobleInstance.startScanning([], false, (error?: Error) => {
|
|
828
759
|
if (error) {
|
|
829
760
|
cleanup();
|
|
830
761
|
logger?.error('[NobleBLE] Failed to start scanning:', error);
|
|
@@ -952,9 +883,9 @@ async function discoverServicesAndCharacteristics(
|
|
|
952
883
|
|
|
953
884
|
// Main discovery logic as async function
|
|
954
885
|
const discoveryPromise = (async (): Promise<CharacteristicPair> => {
|
|
955
|
-
// Step 1: Discover services (
|
|
886
|
+
// Step 1: Discover ALL services (no filter — Pro2 may use different service UUID)
|
|
956
887
|
const services = await new Promise<Service[]>((resolve, reject) => {
|
|
957
|
-
peripheral.discoverServices(
|
|
888
|
+
peripheral.discoverServices([], (error, svc) => {
|
|
958
889
|
if (error) {
|
|
959
890
|
logger?.error('[NobleBLE] Service discovery failed:', error);
|
|
960
891
|
reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -965,33 +896,44 @@ async function discoverServicesAndCharacteristics(
|
|
|
965
896
|
});
|
|
966
897
|
|
|
967
898
|
if (!services || services.length === 0) {
|
|
968
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No
|
|
899
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
969
900
|
}
|
|
970
901
|
|
|
971
|
-
|
|
972
|
-
|
|
902
|
+
logger?.debug('[NobleBLE] services discovered', {
|
|
903
|
+
deviceId: peripheral.id,
|
|
904
|
+
serviceUUIDs: services.map(service => service.uuid),
|
|
905
|
+
});
|
|
973
906
|
|
|
974
|
-
//
|
|
907
|
+
// Find OneKey service — Noble may expose 128-bit UUIDs as short UUID keys.
|
|
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)
|
|
975
925
|
const characteristics = await new Promise<Characteristic[]>((resolve, reject) => {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
} else {
|
|
983
|
-
resolve(chars);
|
|
984
|
-
}
|
|
926
|
+
selectedService.discoverCharacteristics([], (error, chars) => {
|
|
927
|
+
if (error) {
|
|
928
|
+
logger?.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
929
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
930
|
+
} else {
|
|
931
|
+
resolve(chars);
|
|
985
932
|
}
|
|
986
|
-
);
|
|
933
|
+
});
|
|
987
934
|
});
|
|
988
935
|
|
|
989
936
|
// 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
|
-
|
|
995
937
|
let writeCharacteristic: Characteristic | null = null;
|
|
996
938
|
let notifyCharacteristic: Characteristic | null = null;
|
|
997
939
|
|
|
@@ -1006,11 +948,6 @@ async function discoverServicesAndCharacteristics(
|
|
|
1006
948
|
}
|
|
1007
949
|
}
|
|
1008
950
|
|
|
1009
|
-
logger?.info('[NobleBLE] Characteristic discovery result:', {
|
|
1010
|
-
writeFound: !!writeCharacteristic,
|
|
1011
|
-
notifyFound: !!notifyCharacteristic,
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
951
|
if (!writeCharacteristic || !notifyCharacteristic) {
|
|
1015
952
|
logger?.error(
|
|
1016
953
|
'[NobleBLE] Missing characteristics - write:',
|
|
@@ -1024,6 +961,13 @@ async function discoverServicesAndCharacteristics(
|
|
|
1024
961
|
);
|
|
1025
962
|
}
|
|
1026
963
|
|
|
964
|
+
logger?.debug('[NobleBLE] characteristics selected', {
|
|
965
|
+
deviceId: peripheral.id,
|
|
966
|
+
serviceUuid: selectedService.uuid,
|
|
967
|
+
writeUuid: writeCharacteristic.uuid,
|
|
968
|
+
notifyUuid: notifyCharacteristic.uuid,
|
|
969
|
+
});
|
|
970
|
+
|
|
1027
971
|
return { write: writeCharacteristic, notify: notifyCharacteristic };
|
|
1028
972
|
})();
|
|
1029
973
|
|
|
@@ -1320,7 +1264,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1320
1264
|
existingCharacteristics.notify.removeAllListeners('data');
|
|
1321
1265
|
}
|
|
1322
1266
|
notificationCallbacks.delete(deviceId);
|
|
1323
|
-
devicePacketStates.delete(deviceId);
|
|
1324
1267
|
subscribedDevices.delete(deviceId);
|
|
1325
1268
|
// Continue to re-setup the connection properly
|
|
1326
1269
|
}
|
|
@@ -1431,7 +1374,6 @@ async function unsubscribeNotifications(deviceId: string): Promise<void> {
|
|
|
1431
1374
|
// Remove all listeners and clear subscription status
|
|
1432
1375
|
notifyCharacteristic.removeAllListeners('data');
|
|
1433
1376
|
notificationCallbacks.delete(deviceId);
|
|
1434
|
-
devicePacketStates.delete(deviceId);
|
|
1435
1377
|
subscribedDevices.delete(deviceId);
|
|
1436
1378
|
} finally {
|
|
1437
1379
|
// 🔒 CRITICAL: Always clear operation state (even on error)
|
|
@@ -1498,14 +1440,6 @@ async function subscribeNotifications(
|
|
|
1498
1440
|
// Just update the callback without re-subscribing
|
|
1499
1441
|
notificationCallbacks.set(deviceId, callback);
|
|
1500
1442
|
|
|
1501
|
-
// Reset packet state for new session
|
|
1502
|
-
devicePacketStates.set(deviceId, {
|
|
1503
|
-
bufferLength: 0,
|
|
1504
|
-
buffer: [],
|
|
1505
|
-
packetCount: 0,
|
|
1506
|
-
messageId: undefined,
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
1443
|
// 🔒 Clear operation state
|
|
1510
1444
|
subscriptionOperations.set(deviceId, 'idle');
|
|
1511
1445
|
return Promise.resolve();
|
|
@@ -1522,14 +1456,6 @@ async function subscribeNotifications(
|
|
|
1522
1456
|
// Store callback for this device
|
|
1523
1457
|
notificationCallbacks.set(deviceId, callback);
|
|
1524
1458
|
|
|
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
|
-
|
|
1533
1459
|
// Helper: rebuild a clean application-layer subscription
|
|
1534
1460
|
async function rebuildAppSubscription(
|
|
1535
1461
|
deviceId: string,
|
|
@@ -1558,15 +1484,7 @@ async function subscribeNotifications(
|
|
|
1558
1484
|
logger?.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
1559
1485
|
}
|
|
1560
1486
|
|
|
1561
|
-
|
|
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
|
-
}
|
|
1487
|
+
emitRawNotification(deviceId, data);
|
|
1570
1488
|
});
|
|
1571
1489
|
}
|
|
1572
1490
|
|
|
@@ -1581,29 +1499,25 @@ async function subscribeNotifications(
|
|
|
1581
1499
|
|
|
1582
1500
|
// Setup IPC handlers
|
|
1583
1501
|
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.');
|
|
1586
1502
|
try {
|
|
1587
|
-
console.log('[NobleBLE] NOBLE_VERSION_771');
|
|
1588
|
-
|
|
1589
1503
|
// @ts-ignore – electron-log is only available at runtime
|
|
1590
1504
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
1591
1505
|
logger = require('electron-log') as Logger;
|
|
1592
|
-
console.log('[NobleBLE] electron-log loaded successfully.');
|
|
1593
1506
|
|
|
1594
1507
|
// @ts-ignore – electron is only available at runtime
|
|
1595
1508
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
1596
1509
|
const { ipcMain } = require('electron');
|
|
1597
|
-
console.log('[NobleBLE] electron.ipcMain loaded successfully.');
|
|
1598
1510
|
|
|
1599
1511
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1600
1512
|
|
|
1601
1513
|
// Handle enumerate request
|
|
1602
|
-
console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
|
|
1603
1514
|
ipcMain.handle(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, async () => {
|
|
1604
1515
|
try {
|
|
1605
1516
|
const devices = await enumerateDevices();
|
|
1606
|
-
safeLog(logger, '
|
|
1517
|
+
safeLog(logger, 'debug', 'Enumeration completed', {
|
|
1518
|
+
count: devices.length,
|
|
1519
|
+
devices: devices.map(device => ({ id: device.id, name: device.name })),
|
|
1520
|
+
});
|
|
1607
1521
|
return devices;
|
|
1608
1522
|
} catch (error) {
|
|
1609
1523
|
safeLog(logger, 'error', 'Enumeration failed:', error);
|
|
@@ -1648,7 +1562,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1648
1562
|
ipcMain.handle(
|
|
1649
1563
|
EOneKeyBleMessageKeys.NOBLE_BLE_WRITE,
|
|
1650
1564
|
async (_event: IpcMainInvokeEvent, deviceId: string, hexData: string) => {
|
|
1651
|
-
logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
|
|
1652
1565
|
await transmitHexDataToDevice(deviceId, hexData);
|
|
1653
1566
|
}
|
|
1654
1567
|
);
|