@onekeyfe/hd-transport-electron 1.1.27-alpha.41 → 1.1.27-alpha.6

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