@onekeyfe/hd-transport-electron 1.2.0-alpha.66 → 1.2.0-alpha.67

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.
@@ -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-a0b2f06d.js'); });
35
+ const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-d58af248.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-84fea839.js');
5
+ var index = require('./index-5f728113.js');
6
6
 
7
7
 
8
8
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-84fea839.js');
3
+ var index = require('./index-5f728113.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -78,9 +78,6 @@ function softRefreshSubscription(params) {
78
78
  });
79
79
  }
80
80
 
81
- const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
82
- const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
83
-
84
81
  let noble = null;
85
82
  let logger = null;
86
83
  const bluetoothState = {
@@ -103,8 +100,10 @@ const ONEKEY_SERVICE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.
103
100
  const ONEKEY_WRITE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID);
104
101
  const ONEKEY_NOTIFY_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID);
105
102
  const BLUETOOTH_INIT_TIMEOUT = 10000;
106
- const DEVICE_SCAN_TIMEOUT = 5000;
103
+ const DEVICE_SCAN_TIMEOUT = 8000;
104
+ const FAST_SCAN_TIMEOUT = 8000;
107
105
  const DEVICE_CHECK_INTERVAL = 500;
106
+ const CONNECTION_TIMEOUT = 8000;
108
107
  const SERVICE_DISCOVERY_TIMEOUT = 10000;
109
108
  const BLE_CLEANUP_TIMEOUT = 250;
110
109
  const BLE_PACKET_SIZE = 192;
@@ -117,14 +116,10 @@ const ABORTABLE_WRITE_ERROR_PATTERNS = [
117
116
  function isOneKeyPeripheral(peripheral) {
118
117
  var _a, _b;
119
118
  const serviceUuids = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids;
120
- const localName = (_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName;
121
- if (!(localName === null || localName === void 0 ? void 0 : localName.trim()) || hdShared.isPro2FindMyAdvertisementName(localName)) {
122
- return false;
123
- }
124
119
  return (hdShared.hasOnekeyCommunicationService(serviceUuids) &&
125
120
  hdShared.isOnekeyBluetoothDevice({
126
121
  id: peripheral.id,
127
- localName,
122
+ localName: (_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName,
128
123
  serviceUuids,
129
124
  }));
130
125
  }
@@ -514,14 +509,6 @@ function ensureDiscoverListener() {
514
509
  logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already registered');
515
510
  }
516
511
  }
517
- function waitForNobleScanStop(nobleInstance) {
518
- return index.__awaiter(this, void 0, void 0, function* () {
519
- yield runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
520
- timeoutMs: BLE_CLEANUP_TIMEOUT,
521
- timeoutBehavior: 'resolve',
522
- });
523
- });
524
- }
525
512
  function performTargetedScan(targetDeviceId) {
526
513
  return index.__awaiter(this, void 0, void 0, function* () {
527
514
  if (!noble) {
@@ -530,25 +517,6 @@ function performTargetedScan(targetDeviceId) {
530
517
  const nobleInstance = noble;
531
518
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
532
519
  return new Promise((resolve, reject) => {
533
- let settled = false;
534
- const finish = (peripheral, error) => index.__awaiter(this, void 0, void 0, function* () {
535
- if (settled)
536
- return;
537
- settled = true;
538
- if (timeoutId)
539
- clearTimeout(timeoutId);
540
- nobleInstance.removeListener('discover', onDiscover);
541
- yield waitForNobleScanStop(nobleInstance);
542
- if (error) {
543
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
544
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
545
- return;
546
- }
547
- if (peripheral) {
548
- discoveredDevices.set(peripheral.id, peripheral);
549
- }
550
- resolve(peripheral);
551
- });
552
520
  const onDiscover = (peripheral) => {
553
521
  var _a;
554
522
  if (peripheral.id === targetDeviceId && isOneKeyPeripheral(peripheral)) {
@@ -556,17 +524,26 @@ function performTargetedScan(targetDeviceId) {
556
524
  id: peripheral.id,
557
525
  name: (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName,
558
526
  });
559
- finish(peripheral).catch(reject);
527
+ clearTimeout(timeoutId);
528
+ nobleInstance.removeListener('discover', onDiscover);
529
+ nobleInstance.stopScanning();
530
+ discoveredDevices.set(peripheral.id, peripheral);
531
+ resolve(peripheral);
560
532
  }
561
533
  };
562
534
  const timeoutId = setTimeout(() => {
535
+ nobleInstance.removeListener('discover', onDiscover);
536
+ nobleInstance.stopScanning();
563
537
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
564
- finish(null).catch(reject);
565
- }, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
538
+ resolve(null);
539
+ }, FAST_SCAN_TIMEOUT);
566
540
  nobleInstance.on('discover', onDiscover);
567
541
  nobleInstance.startScanning([], false, (error) => {
568
542
  if (error) {
569
- finish(null, error).catch(reject);
543
+ clearTimeout(timeoutId);
544
+ nobleInstance.removeListener('discover', onDiscover);
545
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
546
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
570
547
  return;
571
548
  }
572
549
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
@@ -589,12 +566,12 @@ function enumerateDevices() {
589
566
  return new Promise((resolve, reject) => {
590
567
  const devices = [];
591
568
  let intervalId;
592
- const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
569
+ const cleanup = () => {
593
570
  clearTimeout(timeoutId);
594
571
  if (intervalId)
595
572
  clearInterval(intervalId);
596
- yield waitForNobleScanStop(nobleInstance);
597
- });
573
+ nobleInstance.stopScanning();
574
+ };
598
575
  const checkDevices = () => {
599
576
  discoveredDevices.forEach((peripheral, id) => {
600
577
  var _a;
@@ -610,23 +587,23 @@ function enumerateDevices() {
610
587
  }
611
588
  });
612
589
  };
613
- const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
590
+ const timeoutId = setTimeout(() => {
614
591
  checkDevices();
615
- yield cleanup();
592
+ cleanup();
616
593
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
617
594
  resolve(devices);
618
- }), DEVICE_SCAN_TIMEOUT);
595
+ }, DEVICE_SCAN_TIMEOUT);
619
596
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
620
- nobleInstance.startScanning([], false, (error) => index.__awaiter(this, void 0, void 0, function* () {
597
+ nobleInstance.startScanning([], false, (error) => {
621
598
  if (error) {
622
- yield cleanup();
599
+ cleanup();
623
600
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
624
601
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
625
602
  return;
626
603
  }
627
604
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning started for OneKey devices');
628
605
  intervalId = setInterval(checkDevices, DEVICE_CHECK_INTERVAL);
629
- }));
606
+ });
630
607
  });
631
608
  });
632
609
  }
@@ -635,7 +612,10 @@ function stopScanning() {
635
612
  if (!noble)
636
613
  return;
637
614
  const nobleInstance = noble;
638
- yield waitForNobleScanStop(nobleInstance);
615
+ yield runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
616
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
617
+ timeoutBehavior: 'resolve',
618
+ });
639
619
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
640
620
  });
641
621
  }
@@ -790,7 +770,7 @@ function forceReconnectPeripheral(peripheral, deviceId) {
790
770
  }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
791
771
  }
792
772
  yield runBleCallbackOperation(callback => peripheral.connect(callback), {
793
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
773
+ timeoutMs: CONNECTION_TIMEOUT,
794
774
  timeoutBehavior: 'reject',
795
775
  });
796
776
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
@@ -960,24 +940,12 @@ function connectDevice(deviceId, webContents) {
960
940
  return;
961
941
  }
962
942
  return new Promise((resolve, reject) => {
963
- let connectionTimedOut = false;
964
943
  const timeout = setTimeout(() => {
965
- connectionTimedOut = true;
966
944
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
967
- }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
945
+ }, CONNECTION_TIMEOUT);
968
946
  const connectedPeripheral = peripheral;
969
947
  connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
970
948
  clearTimeout(timeout);
971
- if (connectionTimedOut) {
972
- if (!error) {
973
- try {
974
- connectedPeripheral.disconnect(() => undefined);
975
- }
976
- catch (_a) {
977
- }
978
- }
979
- return;
980
- }
981
949
  if (error) {
982
950
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
983
951
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
@@ -1096,7 +1064,7 @@ function subscribeNotifications(deviceId, callback) {
1096
1064
  timeoutBehavior: 'resolve',
1097
1065
  });
1098
1066
  yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1099
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1067
+ timeoutMs: CONNECTION_TIMEOUT,
1100
1068
  timeoutBehavior: 'reject',
1101
1069
  });
1102
1070
  notifyCharacteristic.on('data', (data) => {
@@ -1 +1 @@
1
- {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAi9ChE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAyJpE"}
1
+ {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AA46ChE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAyJpE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-electron",
3
- "version": "1.2.0-alpha.66",
3
+ "version": "1.2.0-alpha.67",
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.2.0-alpha.66",
29
- "@onekeyfe/hd-shared": "1.2.0-alpha.66",
30
- "@onekeyfe/hd-transport": "1.2.0-alpha.66",
28
+ "@onekeyfe/hd-core": "1.2.0-alpha.67",
29
+ "@onekeyfe/hd-shared": "1.2.0-alpha.67",
30
+ "@onekeyfe/hd-transport": "1.2.0-alpha.67",
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": "81629c086665bb92ce4d52087ae47f084976da79"
39
+ "gitHead": "5fea66e8c65a1fab2ccc099b2811c8749b2487e0"
40
40
  }
@@ -15,7 +15,6 @@ import {
15
15
  createKnownBleUuidAliases,
16
16
  hasOnekeyCommunicationService,
17
17
  isOnekeyBluetoothDevice,
18
- isPro2FindMyAdvertisementName,
19
18
  matchesKnownBleUuid,
20
19
  wait,
21
20
  } from '@onekeyfe/hd-shared';
@@ -23,10 +22,6 @@ import pRetry from 'p-retry';
23
22
 
24
23
  import { safeLog } from './types/noble-extended';
25
24
  import { runBleCallbackOperation, softRefreshSubscription } from './ble-ops';
26
- import {
27
- NOBLE_BLE_CONNECTION_TIMEOUT_MS,
28
- NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
29
- } from './noble-ble-timeouts';
30
25
 
31
26
  import type { IpcMainInvokeEvent, WebContents } from 'electron';
32
27
  import type { Characteristic, Peripheral, Service } from '@stoprocent/noble';
@@ -80,8 +75,10 @@ const ONEKEY_NOTIFY_UUID_ALIASES = createKnownBleUuidAliases(ONEKEY_NOTIFY_CHARA
80
75
 
81
76
  // Timeout and interval constants
82
77
  const BLUETOOTH_INIT_TIMEOUT = 10000; // 10 seconds for Bluetooth initialization
83
- const DEVICE_SCAN_TIMEOUT = 5000; // 5 seconds for device scanning
78
+ const DEVICE_SCAN_TIMEOUT = 8000; // 8 seconds for device scanning (Pro2 has longer advertising interval)
79
+ const FAST_SCAN_TIMEOUT = 8000; // 8 seconds for targeted scanning (Pro2 has longer advertising interval)
84
80
  const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
81
+ const CONNECTION_TIMEOUT = 8000; // 8 seconds for device connection (BLE reconnect after release can be slow)
85
82
  const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
86
83
  const BLE_CLEANUP_TIMEOUT = 250;
87
84
 
@@ -96,19 +93,11 @@ const ABORTABLE_WRITE_ERROR_PATTERNS = [
96
93
 
97
94
  function isOneKeyPeripheral(peripheral: Peripheral) {
98
95
  const serviceUuids = peripheral.advertisement?.serviceUuids;
99
- const localName = peripheral.advertisement?.localName;
100
-
101
- // Noble localName is the current advertisement name, so reject the Pro2
102
- // Find My endpoint before the communication-service fast path accepts it.
103
- if (!localName?.trim() || isPro2FindMyAdvertisementName(localName)) {
104
- return false;
105
- }
106
-
107
96
  return (
108
97
  hasOnekeyCommunicationService(serviceUuids) &&
109
98
  isOnekeyBluetoothDevice({
110
99
  id: peripheral.id,
111
- localName,
100
+ localName: peripheral.advertisement?.localName,
112
101
  serviceUuids,
113
102
  })
114
103
  );
@@ -652,13 +641,6 @@ function ensureDiscoverListener(): void {
652
641
  }
653
642
  }
654
643
 
655
- async function waitForNobleScanStop(nobleInstance: NobleModule): Promise<void> {
656
- await runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
657
- timeoutMs: BLE_CLEANUP_TIMEOUT,
658
- timeoutBehavior: 'resolve',
659
- });
660
- }
661
-
662
644
  // Perform targeted scan for a specific device ID
663
645
  // Uses self-contained local listener pattern - no global state needed
664
646
  async function performTargetedScan(targetDeviceId: string): Promise<Peripheral | null> {
@@ -672,26 +654,6 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
672
654
  logger?.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
673
655
 
674
656
  return new Promise((resolve, reject) => {
675
- let settled = false;
676
-
677
- const finish = async (peripheral: Peripheral | null, error?: Error) => {
678
- if (settled) return;
679
- settled = true;
680
- if (timeoutId) clearTimeout(timeoutId);
681
- nobleInstance.removeListener('discover', onDiscover);
682
- await waitForNobleScanStop(nobleInstance);
683
-
684
- if (error) {
685
- logger?.error('[NobleBLE] Failed to start targeted scan:', error);
686
- reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
687
- return;
688
- }
689
- if (peripheral) {
690
- discoveredDevices.set(peripheral.id, peripheral);
691
- }
692
- resolve(peripheral);
693
- };
694
-
695
657
  // Local discover listener - only matches target device
696
658
  const onDiscover = (peripheral: Peripheral) => {
697
659
  if (peripheral.id === targetDeviceId && isOneKeyPeripheral(peripheral)) {
@@ -699,14 +661,21 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
699
661
  id: peripheral.id,
700
662
  name: peripheral.advertisement?.localName,
701
663
  });
702
- finish(peripheral).catch(reject);
664
+ clearTimeout(timeoutId);
665
+ nobleInstance.removeListener('discover', onDiscover);
666
+ nobleInstance.stopScanning();
667
+ discoveredDevices.set(peripheral.id, peripheral);
668
+ resolve(peripheral);
703
669
  }
704
670
  };
705
671
 
672
+ // Timeout handler - must be after onDiscover so it can reference it
706
673
  const timeoutId = setTimeout(() => {
674
+ nobleInstance.removeListener('discover', onDiscover);
675
+ nobleInstance.stopScanning();
707
676
  logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
708
- finish(null).catch(reject);
709
- }, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
677
+ resolve(null);
678
+ }, FAST_SCAN_TIMEOUT);
710
679
 
711
680
  // Add local listener for this scan
712
681
  nobleInstance.on('discover', onDiscover);
@@ -714,7 +683,10 @@ async function performTargetedScan(targetDeviceId: string): Promise<Peripheral |
714
683
  // Start scanning — no service UUID filter (Pro2 may use different service UUID)
715
684
  nobleInstance.startScanning([], false, (error?: Error) => {
716
685
  if (error) {
717
- finish(null, error).catch(reject);
686
+ clearTimeout(timeoutId);
687
+ nobleInstance.removeListener('discover', onDiscover);
688
+ logger?.error('[NobleBLE] Failed to start targeted scan:', error);
689
+ reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
718
690
  return;
719
691
  }
720
692
  logger?.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
@@ -748,13 +720,11 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
748
720
  const devices: DeviceInfo[] = [];
749
721
  let intervalId: ReturnType<typeof setInterval> | undefined;
750
722
 
751
- // Cleanup function: clears timers and waits until Noble confirms scanning
752
- // has stopped. Resolving enumerate before this callback creates a race with
753
- // an immediately-following connection attempt.
754
- const cleanup = async () => {
723
+ // Cleanup function: clears both timeout and interval
724
+ const cleanup = () => {
755
725
  clearTimeout(timeoutId);
756
726
  if (intervalId) clearInterval(intervalId);
757
- await waitForNobleScanStop(nobleInstance);
727
+ nobleInstance.stopScanning();
758
728
  };
759
729
 
760
730
  // Collect discovered devices into the devices array
@@ -774,10 +744,10 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
774
744
  };
775
745
 
776
746
  // Set timeout for scanning — use longer timeout to catch slow-advertising devices like Pro2
777
- const timeoutId = setTimeout(async () => {
747
+ const timeoutId = setTimeout(() => {
778
748
  // Final collection before resolving — catches devices discovered near the deadline
779
749
  checkDevices();
780
- await cleanup();
750
+ cleanup();
781
751
  logger?.info('[NobleBLE] Scan completed, found devices:', devices.length);
782
752
  resolve(devices);
783
753
  }, DEVICE_SCAN_TIMEOUT);
@@ -785,9 +755,9 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
785
755
  // Start scanning without a service UUID filter so Pro2 advertisements with
786
756
  // short vendor UUIDs can be found, but only OneKey candidates are logged/returned.
787
757
  logger?.info('[NobleBLE] Scanning for OneKey BLE devices');
788
- nobleInstance.startScanning([], false, async (error?: Error) => {
758
+ nobleInstance.startScanning([], false, (error?: Error) => {
789
759
  if (error) {
790
- await cleanup();
760
+ cleanup();
791
761
  logger?.error('[NobleBLE] Failed to start scanning:', error);
792
762
  reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
793
763
  return;
@@ -805,7 +775,10 @@ async function enumerateDevices(): Promise<DeviceInfo[]> {
805
775
  async function stopScanning(): Promise<void> {
806
776
  if (!noble) return;
807
777
  const nobleInstance = noble;
808
- await waitForNobleScanStop(nobleInstance);
778
+ await runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
779
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
780
+ timeoutBehavior: 'resolve',
781
+ });
809
782
  logger?.info('[NobleBLE] Scanning stopped');
810
783
  }
811
784
 
@@ -1023,7 +996,7 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
1023
996
 
1024
997
  // Step 3: Re-establish connection
1025
998
  await runBleCallbackOperation(callback => peripheral.connect(callback), {
1026
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
999
+ timeoutMs: CONNECTION_TIMEOUT,
1027
1000
  timeoutBehavior: 'reject',
1028
1001
  });
1029
1002
  logger?.info('[NobleBLE] Force reconnect successful');
@@ -1295,30 +1268,15 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1295
1268
  }
1296
1269
 
1297
1270
  return new Promise((resolve, reject) => {
1298
- let connectionTimedOut = false;
1299
1271
  const timeout = setTimeout(() => {
1300
- connectionTimedOut = true;
1301
1272
  reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1302
- }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
1273
+ }, CONNECTION_TIMEOUT);
1303
1274
 
1304
1275
  // TypeScript type assertion - peripheral is guaranteed to be defined at this point
1305
1276
  const connectedPeripheral = peripheral as Peripheral;
1306
1277
  connectedPeripheral.connect(async (error: Error | undefined) => {
1307
1278
  clearTimeout(timeout);
1308
1279
 
1309
- // Noble may invoke the callback after the SDK timed out and released the request.
1310
- // Ignore it to avoid initializing disposed commands or leaving an orphaned connection.
1311
- if (connectionTimedOut) {
1312
- if (!error) {
1313
- try {
1314
- connectedPeripheral.disconnect(() => undefined);
1315
- } catch {
1316
- // Best-effort cleanup only.
1317
- }
1318
- }
1319
- return;
1320
- }
1321
-
1322
1280
  if (error) {
1323
1281
  logger?.error('[NobleBLE] Connection failed:', error);
1324
1282
  reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
@@ -1492,7 +1450,7 @@ async function subscribeNotifications(
1492
1450
  timeoutBehavior: 'resolve',
1493
1451
  });
1494
1452
  await runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1495
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1453
+ timeoutMs: CONNECTION_TIMEOUT,
1496
1454
  timeoutBehavior: 'reject',
1497
1455
  });
1498
1456
 
@@ -1,3 +0,0 @@
1
- export declare const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
2
- export declare const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
3
- //# sourceMappingURL=noble-ble-timeouts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"noble-ble-timeouts.d.ts","sourceRoot":"","sources":["../src/noble-ble-timeouts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,OAAQ,CAAC;AACxD,eAAO,MAAM,+BAA+B,QAAS,CAAC"}
@@ -1,354 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import { EOneKeyBleMessageKeys } from '@onekeyfe/hd-shared';
3
-
4
- import {
5
- NOBLE_BLE_CONNECTION_TIMEOUT_MS,
6
- NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
7
- } from '../noble-ble-timeouts';
8
-
9
- import type { WebContents } from 'electron';
10
-
11
- type IpcHandler = (...args: unknown[]) => Promise<unknown> | unknown;
12
-
13
- const createPeripheral = (id: string, localName?: string) => ({
14
- id,
15
- state: 'disconnected',
16
- advertisement: {
17
- localName,
18
- serviceUuids: ['0001', 'fffd'],
19
- },
20
- });
21
-
22
- describe('Electron Noble BLE device discovery', () => {
23
- afterEach(() => {
24
- jest.useRealTimers();
25
- jest.resetModules();
26
- jest.clearAllMocks();
27
- });
28
-
29
- test('allows enough time for a slow targeted scan and connection', () => {
30
- expect(NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS).toBe(5_000);
31
- expect(NOBLE_BLE_CONNECTION_TIMEOUT_MS).toBe(10_000);
32
- });
33
-
34
- test('waits for Noble to stop scanning before enumeration resolves', async () => {
35
- jest.useFakeTimers({ doNotFake: ['performance'] });
36
-
37
- const handlers = new Map<string, IpcHandler>();
38
- const ipcMain = {
39
- handle: jest.fn((channel: string, handler: IpcHandler) => {
40
- handlers.set(channel, handler);
41
- }),
42
- };
43
- const noble = new EventEmitter() as EventEmitter & {
44
- state: string;
45
- startScanning: jest.Mock;
46
- stopScanning: jest.Mock;
47
- };
48
- let resolveScanStarted = () => undefined;
49
- const scanStarted = new Promise<void>(resolve => {
50
- resolveScanStarted = resolve;
51
- });
52
- let stopScanningCallback: (() => void) | undefined;
53
- noble.state = 'poweredOn';
54
- noble.startScanning = jest.fn((_services, _duplicates, callback) => {
55
- callback?.();
56
- noble.emit('discover', createPeripheral('onekey-device', 'Pro2 A1B2'));
57
- resolveScanStarted();
58
- });
59
- noble.stopScanning = jest.fn(callback => {
60
- stopScanningCallback = callback;
61
- });
62
-
63
- jest.doMock('@stoprocent/noble', () => noble);
64
- jest.doMock('electron', () => ({ ipcMain }));
65
- jest.doMock('electron-log', () => ({
66
- info: jest.fn(),
67
- debug: jest.fn(),
68
- error: jest.fn(),
69
- }));
70
-
71
- const { setupNobleBleHandlers } = await import('../noble-ble-handler');
72
- setupNobleBleHandlers({
73
- on: jest.fn(),
74
- send: jest.fn(),
75
- } as unknown as WebContents);
76
-
77
- const enumerate = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE);
78
- if (!enumerate) {
79
- throw new Error('Electron Noble BLE enumerate handler was not registered');
80
- }
81
-
82
- let enumerationResolved = false;
83
- const devicesPromise = Promise.resolve(enumerate()).then(devices => {
84
- enumerationResolved = true;
85
- return devices;
86
- });
87
- await scanStarted;
88
- jest.advanceTimersByTime(5_000);
89
- await Promise.resolve();
90
-
91
- expect(noble.stopScanning).toHaveBeenCalledTimes(1);
92
- expect(enumerationResolved).toBe(false);
93
-
94
- stopScanningCallback?.();
95
- await expect(devicesPromise).resolves.toEqual([
96
- expect.objectContaining({ id: 'onekey-device' }),
97
- ]);
98
- });
99
-
100
- test('waits for a targeted scan to stop before connecting', async () => {
101
- jest.useFakeTimers({ doNotFake: ['performance'] });
102
-
103
- const handlers = new Map<string, IpcHandler>();
104
- const ipcMain = {
105
- handle: jest.fn((channel: string, handler: IpcHandler) => {
106
- handlers.set(channel, handler);
107
- }),
108
- };
109
- const noble = new EventEmitter() as EventEmitter & {
110
- state: string;
111
- startScanning: jest.Mock;
112
- stopScanning: jest.Mock;
113
- };
114
- let resolveScanStarted = () => undefined;
115
- const scanStarted = new Promise<void>(resolve => {
116
- resolveScanStarted = resolve;
117
- });
118
- let stopScanningCallback: (() => void) | undefined;
119
- const peripheral = Object.assign(
120
- new EventEmitter(),
121
- createPeripheral('target-device', 'Pro2 A1B2'),
122
- {
123
- connect: jest.fn((callback: (error?: Error) => void) => {
124
- callback(new Error('expected test connection failure'));
125
- }),
126
- }
127
- );
128
- noble.state = 'poweredOn';
129
- noble.startScanning = jest.fn((_services, _duplicates, callback) => {
130
- callback?.();
131
- noble.emit('discover', peripheral);
132
- resolveScanStarted();
133
- });
134
- noble.stopScanning = jest.fn(callback => {
135
- stopScanningCallback = callback;
136
- });
137
-
138
- jest.doMock('@stoprocent/noble', () => noble);
139
- jest.doMock('electron', () => ({ ipcMain }));
140
- jest.doMock('electron-log', () => ({
141
- info: jest.fn(),
142
- debug: jest.fn(),
143
- error: jest.fn(),
144
- }));
145
-
146
- const { setupNobleBleHandlers } = await import('../noble-ble-handler');
147
- setupNobleBleHandlers({
148
- on: jest.fn(),
149
- send: jest.fn(),
150
- } as unknown as WebContents);
151
-
152
- const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
153
- if (!connect) {
154
- throw new Error('Electron Noble BLE connect handler was not registered');
155
- }
156
-
157
- const connectPromise = Promise.resolve(connect(undefined, 'target-device'));
158
- await scanStarted;
159
-
160
- expect(noble.stopScanning).toHaveBeenCalledTimes(1);
161
- expect(peripheral.connect).not.toHaveBeenCalled();
162
-
163
- stopScanningCallback?.();
164
- await expect(connectPromise).rejects.toThrow('expected test connection failure');
165
- expect(peripheral.connect).toHaveBeenCalledTimes(1);
166
- });
167
-
168
- test('disconnects a connection callback that arrives after timeout', async () => {
169
- jest.useFakeTimers({ doNotFake: ['performance'] });
170
-
171
- const handlers = new Map<string, IpcHandler>();
172
- const ipcMain = {
173
- handle: jest.fn((channel: string, handler: IpcHandler) => {
174
- handlers.set(channel, handler);
175
- }),
176
- };
177
- const noble = new EventEmitter() as EventEmitter & {
178
- state: string;
179
- startScanning: jest.Mock;
180
- stopScanning: jest.Mock;
181
- };
182
- let connectCallback: ((error?: Error) => void) | undefined;
183
- let resolveConnectStarted = () => undefined;
184
- const connectStarted = new Promise<void>(resolve => {
185
- resolveConnectStarted = resolve;
186
- });
187
- const peripheral = Object.assign(
188
- new EventEmitter(),
189
- createPeripheral('slow-device', 'Pro2 A1B2'),
190
- {
191
- connect: jest.fn((callback: (error?: Error) => void) => {
192
- connectCallback = callback;
193
- resolveConnectStarted();
194
- }),
195
- disconnect: jest.fn((callback: () => void) => callback()),
196
- }
197
- );
198
- noble.state = 'poweredOn';
199
- noble.startScanning = jest.fn((_services, _duplicates, callback) => {
200
- callback?.();
201
- noble.emit('discover', peripheral);
202
- });
203
- noble.stopScanning = jest.fn(callback => callback?.());
204
-
205
- jest.doMock('@stoprocent/noble', () => noble);
206
- jest.doMock('electron', () => ({ ipcMain }));
207
- jest.doMock('electron-log', () => ({
208
- info: jest.fn(),
209
- debug: jest.fn(),
210
- error: jest.fn(),
211
- }));
212
-
213
- const { setupNobleBleHandlers } = await import('../noble-ble-handler');
214
- setupNobleBleHandlers({
215
- on: jest.fn(),
216
- send: jest.fn(),
217
- } as unknown as WebContents);
218
-
219
- const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
220
- if (!connect) {
221
- throw new Error('Electron Noble BLE connect handler was not registered');
222
- }
223
-
224
- const connectPromise = Promise.resolve(connect(undefined, 'slow-device'));
225
- await connectStarted;
226
- expect(peripheral.connect).toHaveBeenCalledTimes(1);
227
-
228
- jest.advanceTimersByTime(NOBLE_BLE_CONNECTION_TIMEOUT_MS);
229
- await expect(connectPromise).rejects.toThrow('Connection timeout');
230
-
231
- connectCallback?.();
232
- await Promise.resolve();
233
- expect(peripheral.disconnect).toHaveBeenCalledTimes(1);
234
- });
235
-
236
- test('does not enumerate a Pro2 Find My advertisement with the communication service', async () => {
237
- jest.useFakeTimers({ doNotFake: ['performance'] });
238
-
239
- const handlers = new Map<string, IpcHandler>();
240
- const ipcMain = {
241
- handle: jest.fn((channel: string, handler: IpcHandler) => {
242
- handlers.set(channel, handler);
243
- }),
244
- };
245
- const noble = new EventEmitter() as EventEmitter & {
246
- state: string;
247
- startScanning: jest.Mock;
248
- stopScanning: jest.Mock;
249
- };
250
- let resolveScanStarted = () => undefined;
251
- const scanStarted = new Promise<void>(resolve => {
252
- resolveScanStarted = resolve;
253
- });
254
- noble.state = 'poweredOn';
255
- noble.startScanning = jest.fn((_services, _duplicates, callback) => {
256
- callback?.();
257
- noble.emit('discover', createPeripheral('find-my-device', 'Pro2 A1B2 - Find My'));
258
- noble.emit('discover', createPeripheral('onekey-device', 'Pro2 A1B2'));
259
- resolveScanStarted();
260
- });
261
- noble.stopScanning = jest.fn(callback => callback?.());
262
-
263
- jest.doMock('@stoprocent/noble', () => noble);
264
- jest.doMock('electron', () => ({ ipcMain }));
265
- jest.doMock('electron-log', () => ({
266
- info: jest.fn(),
267
- debug: jest.fn(),
268
- error: jest.fn(),
269
- }));
270
-
271
- const { setupNobleBleHandlers } = await import('../noble-ble-handler');
272
- setupNobleBleHandlers({
273
- on: jest.fn(),
274
- send: jest.fn(),
275
- } as unknown as WebContents);
276
-
277
- const enumerate = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE);
278
- if (!enumerate) {
279
- throw new Error('Electron Noble BLE enumerate handler was not registered');
280
- }
281
-
282
- const devicesPromise = Promise.resolve(enumerate());
283
- await scanStarted;
284
- jest.advanceTimersByTime(5000);
285
-
286
- await expect(devicesPromise).resolves.toEqual([
287
- expect.objectContaining({
288
- id: 'onekey-device',
289
- name: 'Pro2 A1B2',
290
- }),
291
- ]);
292
- });
293
-
294
- test('does not enumerate a Find My peripheral first discovered without a name', async () => {
295
- jest.useFakeTimers({ doNotFake: ['performance'] });
296
-
297
- const handlers = new Map<string, IpcHandler>();
298
- const ipcMain = {
299
- handle: jest.fn((channel: string, handler: IpcHandler) => {
300
- handlers.set(channel, handler);
301
- }),
302
- };
303
- const noble = new EventEmitter() as EventEmitter & {
304
- state: string;
305
- startScanning: jest.Mock;
306
- stopScanning: jest.Mock;
307
- };
308
- let resolveScanStarted = () => undefined;
309
- const scanStarted = new Promise<void>(resolve => {
310
- resolveScanStarted = resolve;
311
- });
312
- const findMyPeripheral = createPeripheral('find-my-device');
313
- noble.state = 'poweredOn';
314
- noble.startScanning = jest.fn((_services, _duplicates, callback) => {
315
- callback?.();
316
- noble.emit('discover', findMyPeripheral);
317
- findMyPeripheral.advertisement.localName = 'Pro2 A1B2 - Find My';
318
- noble.emit('discover', findMyPeripheral);
319
- noble.emit('discover', createPeripheral('onekey-device', 'Pro2 A1B2'));
320
- resolveScanStarted();
321
- });
322
- noble.stopScanning = jest.fn(callback => callback?.());
323
-
324
- jest.doMock('@stoprocent/noble', () => noble);
325
- jest.doMock('electron', () => ({ ipcMain }));
326
- jest.doMock('electron-log', () => ({
327
- info: jest.fn(),
328
- debug: jest.fn(),
329
- error: jest.fn(),
330
- }));
331
-
332
- const { setupNobleBleHandlers } = await import('../noble-ble-handler');
333
- setupNobleBleHandlers({
334
- on: jest.fn(),
335
- send: jest.fn(),
336
- } as unknown as WebContents);
337
-
338
- const enumerate = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE);
339
- if (!enumerate) {
340
- throw new Error('Electron Noble BLE enumerate handler was not registered');
341
- }
342
-
343
- const devicesPromise = Promise.resolve(enumerate());
344
- await scanStarted;
345
- jest.advanceTimersByTime(5000);
346
-
347
- await expect(devicesPromise).resolves.toEqual([
348
- expect.objectContaining({
349
- id: 'onekey-device',
350
- name: 'Pro2 A1B2',
351
- }),
352
- ]);
353
- });
354
- });
@@ -1,2 +0,0 @@
1
- export const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5_000;
2
- export const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10_000;