@onekeyfe/hd-transport-electron 1.2.2-alpha.100 → 1.2.2-alpha.102

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-b37d926c.js');
3
+ var index = require('./index-8e6acfc1.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -9,6 +9,15 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
9
9
  var pRetry__default = /*#__PURE__*/_interopDefaultLegacy(pRetry);
10
10
 
11
11
  const BLE_ATT_HEADER_BYTES = 3;
12
+ function resolveNobleAttMtu(reportedMtu, platform = process.platform) {
13
+ if (typeof reportedMtu !== 'number' || !Number.isFinite(reportedMtu) || reportedMtu <= 0) {
14
+ return undefined;
15
+ }
16
+ const normalizedMtu = Math.floor(reportedMtu);
17
+ return platform === 'darwin' || platform === 'win32'
18
+ ? normalizedMtu + BLE_ATT_HEADER_BYTES
19
+ : normalizedMtu;
20
+ }
12
21
  function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
13
22
  if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
14
23
  return fallbackPacketCapacity;
@@ -87,10 +96,54 @@ function softRefreshSubscription(params) {
87
96
  }
88
97
 
89
98
  const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
90
- const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
99
+ const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10000;
91
100
 
92
101
  let noble = null;
93
102
  let logger = null;
103
+ let disposing = false;
104
+ let disposePromise;
105
+ let removeIpcHandlers;
106
+ const pendingCancellations = new Set();
107
+ function assertBleActive() {
108
+ if (disposing) {
109
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down');
110
+ }
111
+ }
112
+ function createNobleBleConnectionError(error, messagePrefix = '') {
113
+ const errorMessage = error.message;
114
+ const isInvalidMacOsBond = (error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain') ||
115
+ (error.nativeErrorCode === 15 && error.nativeErrorDomain === 'CBATTErrorDomain');
116
+ if (isInvalidMacOsBond) {
117
+ const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
118
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
119
+ nativeErrorMessage,
120
+ });
121
+ }
122
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
123
+ }
124
+ function createNobleBleIpcErrorResponse(error) {
125
+ const candidate = error;
126
+ const errorCode = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.errorCode) === 'number' ? candidate.errorCode : hdShared.HardwareErrorCode.UnknownError;
127
+ const message = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.message) === 'string' ? candidate.message : String(error !== null && error !== void 0 ? error : 'Unknown error');
128
+ const name = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.name) === 'string' ? candidate.name : 'Error';
129
+ let params;
130
+ if ((candidate === null || candidate === void 0 ? void 0 : candidate.params) !== undefined) {
131
+ try {
132
+ const serializedParams = JSON.stringify(candidate.params);
133
+ params = serializedParams === undefined ? undefined : JSON.parse(serializedParams);
134
+ }
135
+ catch (_a) {
136
+ params = undefined;
137
+ }
138
+ }
139
+ return {
140
+ type: 'NobleBleIpcError',
141
+ success: false,
142
+ error: Object.assign({ name,
143
+ message,
144
+ errorCode }, (params !== undefined ? { params } : {})),
145
+ };
146
+ }
94
147
  const bluetoothState = {
95
148
  available: false,
96
149
  unsupported: false,
@@ -100,6 +153,57 @@ let persistentStateListener = null;
100
153
  let persistentDiscoverListener = null;
101
154
  const discoveredDevices = new Map();
102
155
  const connectedDevices = new Map();
156
+ const connectingDevices = new Map();
157
+ let nextConnectionGeneration = 0;
158
+ function connectPeripheralWithCancellation(peripheral, deviceId, messagePrefix = '') {
159
+ assertBleActive();
160
+ return new Promise((resolve, reject) => {
161
+ var _a;
162
+ const generation = ++nextConnectionGeneration;
163
+ let settled = false;
164
+ const settle = (settler) => {
165
+ var _a;
166
+ if (settled)
167
+ return false;
168
+ settled = true;
169
+ if (((_a = connectingDevices.get(deviceId)) === null || _a === void 0 ? void 0 : _a.generation) === generation) {
170
+ connectingDevices.delete(deviceId);
171
+ }
172
+ settler();
173
+ return true;
174
+ };
175
+ const pendingConnection = {
176
+ peripheral,
177
+ generation,
178
+ cancel: error => {
179
+ settle(() => reject(error));
180
+ },
181
+ };
182
+ (_a = connectingDevices
183
+ .get(deviceId)) === null || _a === void 0 ? void 0 : _a.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connect attempt was superseded`));
184
+ connectingDevices.set(deviceId, pendingConnection);
185
+ peripheral.connect(error => {
186
+ if (error) {
187
+ settle(() => reject(createNobleBleConnectionError(error, messagePrefix)));
188
+ return;
189
+ }
190
+ if (!settle(() => {
191
+ connectedDevices.set(deviceId, peripheral);
192
+ resolve();
193
+ })) {
194
+ const activeConnection = connectingDevices.get(deviceId);
195
+ if (!activeConnection || activeConnection.peripheral !== peripheral) {
196
+ try {
197
+ peripheral.removeAllListeners('disconnect');
198
+ peripheral.disconnect(() => undefined);
199
+ }
200
+ catch (_a) {
201
+ }
202
+ }
203
+ }
204
+ });
205
+ });
206
+ }
103
207
  const pairedDevices = new Set();
104
208
  const deviceCharacteristics = new Map();
105
209
  const notificationCallbacks = new Map();
@@ -116,7 +220,8 @@ const DEVICE_SCAN_TIMEOUT = 5000;
116
220
  const DEVICE_CHECK_INTERVAL = 500;
117
221
  const SERVICE_DISCOVERY_TIMEOUT = 10000;
118
222
  const BLE_CLEANUP_TIMEOUT = 250;
119
- const BLE_IDLE_DISCONNECT_MS = 3 * 60000;
223
+ const BLE_DISCONNECT_CONFIRM_TIMEOUT_MS = 3000;
224
+ const BLE_IDLE_DISCONNECT_MS = 20000;
120
225
  const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
121
226
  const BLE_PACKET_SIZE_FALLBACK = 192;
122
227
  const BLE_PACKET_SIZE_MAXIMUM = 244;
@@ -237,11 +342,13 @@ function updateBluetoothState(state) {
237
342
  }
238
343
  function initializeNoble() {
239
344
  return index.__awaiter(this, void 0, void 0, function* () {
345
+ assertBleActive();
240
346
  if (noble)
241
347
  return;
242
348
  try {
243
349
  noble = require('@stoprocent/noble');
244
350
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
351
+ setupPersistentStateListener();
245
352
  yield new Promise((resolve, reject) => {
246
353
  if (!noble) {
247
354
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not initialized'));
@@ -251,11 +358,12 @@ function initializeNoble() {
251
358
  resolve();
252
359
  return;
253
360
  }
254
- setupPersistentStateListener();
255
361
  const timeout = setTimeout(() => {
362
+ cleanup();
256
363
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
257
364
  }, BLUETOOTH_INIT_TIMEOUT);
258
365
  const cleanup = () => {
366
+ pendingCancellations.delete(cancel);
259
367
  clearTimeout(timeout);
260
368
  if (noble) {
261
369
  noble.removeListener('stateChange', onStateChange);
@@ -280,8 +388,14 @@ function initializeNoble() {
280
388
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
281
389
  }
282
390
  };
391
+ const cancel = () => {
392
+ cleanup();
393
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
394
+ };
395
+ pendingCancellations.add(cancel);
283
396
  noble.on('stateChange', onStateChange);
284
397
  });
398
+ assertBleActive();
285
399
  if (!persistentDiscoverListener) {
286
400
  persistentDiscoverListener = (peripheral) => {
287
401
  handleDeviceDiscovered(peripheral);
@@ -328,6 +442,8 @@ function broadcastToAllWebContents(channel, payload) {
328
442
  }
329
443
  function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
330
444
  clearIdleDisconnect(deviceId);
445
+ if (disposing)
446
+ return;
331
447
  idleDisconnectTimers.set(deviceId, setTimeout(() => {
332
448
  var _a;
333
449
  idleDisconnectTimers.delete(deviceId);
@@ -336,7 +452,14 @@ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle
336
452
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device', { deviceId, reason });
337
453
  const peripheral = connectedDevices.get(deviceId);
338
454
  const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
339
- const pending = disconnectDevice(deviceId)
455
+ const pending = unsubscribeNotifications(deviceId)
456
+ .catch(error => {
457
+ logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Keep-alive unsubscribe failed, disconnecting anyway', {
458
+ deviceId,
459
+ error: error instanceof Error ? error.message : String(error),
460
+ });
461
+ })
462
+ .then(() => disconnectDevice(deviceId))
340
463
  .then(() => {
341
464
  broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
342
465
  id: deviceId,
@@ -356,7 +479,7 @@ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle
356
479
  }, ms));
357
480
  }
358
481
  function cleanupDevice(deviceId, webContents, options = {}) {
359
- var _a;
482
+ var _a, _b;
360
483
  const { cleanupConnection = true, cleanupDiscoveredCache = false, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', disconnectReason = hdShared.EBleDisconnectReason.DeviceDisconnected, } = options;
361
484
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device cleanup', {
362
485
  deviceId,
@@ -370,6 +493,8 @@ function cleanupDevice(deviceId, webContents, options = {}) {
370
493
  const peripheral = connectedDevices.get(deviceId);
371
494
  const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
372
495
  if (cleanupConnection) {
496
+ (_b = connectingDevices
497
+ .get(deviceId)) === null || _b === void 0 ? void 0 : _b.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} disconnected while connecting`));
373
498
  const disconnectEntry = deviceDisconnectListeners.get(deviceId);
374
499
  if (disconnectEntry) {
375
500
  disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
@@ -409,6 +534,7 @@ function handleDeviceDisconnect(deviceId, webContents) {
409
534
  });
410
535
  cleanupDevice(deviceId, webContents, {
411
536
  cleanupConnection: true,
537
+ cleanupDiscoveredCache: true,
412
538
  sendDisconnectEvent: true,
413
539
  cancelOperations: true,
414
540
  reason: 'auto-disconnect',
@@ -432,13 +558,14 @@ function setupMtuListener(peripheral, deviceId, webContents) {
432
558
  existing.peripheral.removeListener('mtu', existing.listener);
433
559
  }
434
560
  const listener = (mtu) => {
435
- if (!Number.isFinite(mtu) || mtu <= 0)
561
+ const normalizedMtu = resolveNobleAttMtu(mtu);
562
+ if (normalizedMtu === undefined)
436
563
  return;
437
564
  if (webContents.isDestroyed())
438
565
  return;
439
566
  webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
440
567
  id: deviceId,
441
- mtu,
568
+ mtu: normalizedMtu,
442
569
  });
443
570
  };
444
571
  deviceMtuListeners.set(deviceId, { peripheral, listener });
@@ -446,6 +573,7 @@ function setupMtuListener(peripheral, deviceId, webContents) {
446
573
  }
447
574
  function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
448
575
  return index.__awaiter(this, void 0, void 0, function* () {
576
+ assertBleActive();
449
577
  return new Promise((resolve, reject) => {
450
578
  writeCharacteristic.write(buffer, true, (error) => {
451
579
  if (error) {
@@ -545,7 +673,7 @@ function transmitHexDataToDevice(deviceId, hexData, options) {
545
673
  armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
546
674
  const toBuffer = Buffer.from(hexData, 'hex');
547
675
  const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
548
- const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
676
+ const packetCapacity = resolveBlePacketCapacity(resolveNobleAttMtu(peripheral.mtu), BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
549
677
  const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
550
678
  if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
551
679
  const writeCharacteristic = doGetWriteCharacteristic();
@@ -590,18 +718,37 @@ function transmitHexDataToDevice(deviceId, hexData, options) {
590
718
  }
591
719
  });
592
720
  }
721
+ const BLE_COLD_CONNECT_SCAN_TIMEOUT_MS = 1500;
722
+ const SERVICE_DISCOVERY_MAX_ATTEMPTS = 2;
723
+ const bleNamesById = new Map();
724
+ function rememberBleName(deviceId, name) {
725
+ const trimmed = name === null || name === void 0 ? void 0 : name.trim();
726
+ if (trimmed) {
727
+ bleNamesById.set(deviceId, trimmed);
728
+ }
729
+ }
730
+ function resolveBleName(deviceId) {
731
+ var _a, _b, _c, _d, _e, _f;
732
+ const live = ((_c = (_b = (_a = connectedDevices.get(deviceId)) === null || _a === void 0 ? void 0 : _a.advertisement) === null || _b === void 0 ? void 0 : _b.localName) === null || _c === void 0 ? void 0 : _c.trim()) ||
733
+ ((_f = (_e = (_d = discoveredDevices.get(deviceId)) === null || _d === void 0 ? void 0 : _d.advertisement) === null || _e === void 0 ? void 0 : _e.localName) === null || _f === void 0 ? void 0 : _f.trim());
734
+ return live || bleNamesById.get(deviceId);
735
+ }
736
+ function shouldConnectByIdFirst(deviceId) {
737
+ return hdShared.isPro2FamilyBleName(resolveBleName(deviceId));
738
+ }
593
739
  function handleDeviceDiscovered(peripheral) {
594
- var _a, _b;
740
+ var _a, _b, _c;
595
741
  if (!isOneKeyPeripheral(peripheral)) {
596
742
  return;
597
743
  }
598
744
  const isNewDevice = !discoveredDevices.has(peripheral.id);
599
745
  discoveredDevices.set(peripheral.id, peripheral);
746
+ rememberBleName(peripheral.id, (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName);
600
747
  if (isNewDevice) {
601
748
  logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] OneKey BLE device discovered', {
602
749
  deviceId: peripheral.id,
603
- name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
604
- serviceUUIDs: ((_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.serviceUuids) || [],
750
+ name: ((_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device',
751
+ serviceUUIDs: ((_c = peripheral.advertisement) === null || _c === void 0 ? void 0 : _c.serviceUuids) || [],
605
752
  });
606
753
  }
607
754
  }
@@ -627,8 +774,9 @@ function waitForNobleScanStop(nobleInstance) {
627
774
  });
628
775
  });
629
776
  }
630
- function performTargetedScan(targetDeviceId) {
777
+ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS) {
631
778
  return index.__awaiter(this, void 0, void 0, function* () {
779
+ assertBleActive();
632
780
  if (!noble) {
633
781
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
634
782
  }
@@ -640,6 +788,7 @@ function performTargetedScan(targetDeviceId) {
640
788
  if (settled)
641
789
  return;
642
790
  settled = true;
791
+ pendingCancellations.delete(cancel);
643
792
  if (timeoutId)
644
793
  clearTimeout(timeoutId);
645
794
  nobleInstance.removeListener('discover', onDiscover);
@@ -649,6 +798,7 @@ function performTargetedScan(targetDeviceId) {
649
798
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
650
799
  return;
651
800
  }
801
+ assertBleActive();
652
802
  if (peripheral) {
653
803
  discoveredDevices.set(peripheral.id, peripheral);
654
804
  }
@@ -667,7 +817,11 @@ function performTargetedScan(targetDeviceId) {
667
817
  const timeoutId = setTimeout(() => {
668
818
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
669
819
  finish(null).catch(reject);
670
- }, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
820
+ }, timeoutMs);
821
+ const cancel = () => {
822
+ finish(null, new Error('Noble BLE is shutting down')).catch(reject);
823
+ };
824
+ pendingCancellations.add(cancel);
671
825
  nobleInstance.on('discover', onDiscover);
672
826
  nobleInstance.startScanning([], true, (error) => {
673
827
  if (error) {
@@ -687,14 +841,18 @@ function enumerateDevices() {
687
841
  if (!noble) {
688
842
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
689
843
  }
844
+ assertBleActive();
690
845
  const nobleInstance = noble;
691
846
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting device enumeration');
692
847
  discoveredDevices.clear();
693
848
  ensureDiscoverListener();
694
849
  return new Promise((resolve, reject) => {
695
850
  const devices = [];
851
+ let settled = false;
696
852
  let intervalId;
697
853
  const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
854
+ settled = true;
855
+ pendingCancellations.delete(cancel);
698
856
  clearTimeout(timeoutId);
699
857
  if (intervalId)
700
858
  clearInterval(intervalId);
@@ -716,13 +874,21 @@ function enumerateDevices() {
716
874
  connectedDevices.forEach(pushDevice);
717
875
  };
718
876
  const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
877
+ if (settled)
878
+ return;
719
879
  checkDevices();
720
880
  yield cleanup();
721
881
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
722
882
  resolve(devices);
723
883
  }), DEVICE_SCAN_TIMEOUT);
884
+ const cancel = () => {
885
+ cleanup().then(() => resolve([]), reject);
886
+ };
887
+ pendingCancellations.add(cancel);
724
888
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
725
889
  nobleInstance.startScanning([], true, (error) => index.__awaiter(this, void 0, void 0, function* () {
890
+ if (settled)
891
+ return;
726
892
  if (error) {
727
893
  yield cleanup();
728
894
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
@@ -763,12 +929,14 @@ function getDevice(deviceId) {
763
929
  const peripheral = discoveredDevices.get(deviceId);
764
930
  if (peripheral) {
765
931
  const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
766
- return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}));
932
+ const mtu = resolveNobleAttMtu(peripheral.mtu);
933
+ return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (mtu === undefined ? {} : { mtu }));
767
934
  }
768
935
  const connectedPeripheral = connectedDevices.get(deviceId);
769
936
  if (connectedPeripheral) {
770
937
  const deviceName = ((_b = connectedPeripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device';
771
- return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}));
938
+ const mtu = resolveNobleAttMtu(connectedPeripheral.mtu);
939
+ return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (mtu === undefined ? {} : { mtu }));
772
940
  }
773
941
  return {
774
942
  commType: 'electron-ble',
@@ -779,13 +947,16 @@ function getDevice(deviceId) {
779
947
  }
780
948
  function discoverServicesAndCharacteristics(peripheral) {
781
949
  return index.__awaiter(this, void 0, void 0, function* () {
950
+ assertBleActive();
782
951
  let timeoutId;
783
952
  let onDisconnect;
784
953
  const cleanup = () => {
785
954
  if (timeoutId)
786
955
  clearTimeout(timeoutId);
787
- if (onDisconnect)
956
+ if (onDisconnect) {
788
957
  peripheral.removeListener('disconnect', onDisconnect);
958
+ pendingCancellations.delete(onDisconnect);
959
+ }
789
960
  };
790
961
  const timeoutPromise = new Promise((_, reject) => {
791
962
  timeoutId = setTimeout(() => {
@@ -798,11 +969,12 @@ function discoverServicesAndCharacteristics(peripheral) {
798
969
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Device disconnected during service discovery');
799
970
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'Device disconnected during service discovery'));
800
971
  };
972
+ pendingCancellations.add(onDisconnect);
801
973
  peripheral.once('disconnect', onDisconnect);
802
974
  });
803
975
  const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
804
976
  const services = yield new Promise((resolve, reject) => {
805
- peripheral.discoverServices([], (error, svc) => {
977
+ peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
806
978
  if (error) {
807
979
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
808
980
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
@@ -812,8 +984,9 @@ function discoverServicesAndCharacteristics(peripheral) {
812
984
  }
813
985
  });
814
986
  });
987
+ assertBleActive();
815
988
  if (!services || services.length === 0) {
816
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No services found');
989
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
817
990
  }
818
991
  logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] services discovered', {
819
992
  deviceId: peripheral.id,
@@ -884,12 +1057,8 @@ function forceReconnectPeripheral(peripheral, deviceId) {
884
1057
  callback();
885
1058
  }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
886
1059
  }
887
- yield runBleCallbackOperation(callback => peripheral.connect(callback), {
888
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
889
- timeoutBehavior: 'reject',
890
- });
1060
+ yield connectPeripheralWithCancellation(peripheral, deviceId);
891
1061
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
892
- connectedDevices.set(deviceId, peripheral);
893
1062
  yield hdShared.wait(500);
894
1063
  });
895
1064
  }
@@ -906,17 +1075,7 @@ function freshScanAndDiscover(deviceId, webContents) {
906
1075
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found in fresh scan`);
907
1076
  }
908
1077
  discoveredDevices.set(deviceId, freshPeripheral);
909
- yield new Promise((resolve, reject) => {
910
- freshPeripheral.connect((error) => {
911
- if (error) {
912
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Fresh peripheral connection failed: ${error.message}`));
913
- }
914
- else {
915
- connectedDevices.set(deviceId, freshPeripheral);
916
- resolve();
917
- }
918
- });
919
- });
1078
+ yield connectPeripheralWithCancellation(freshPeripheral, deviceId, 'Fresh peripheral connection failed: ');
920
1079
  setupDisconnectListener(freshPeripheral, deviceId, webContents);
921
1080
  yield hdShared.wait(500);
922
1081
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
@@ -930,11 +1089,11 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
930
1089
  deviceId,
931
1090
  peripheralState: peripheral.state,
932
1091
  attempt: attemptNumber,
933
- maxRetries: 5,
1092
+ maxRetries: SERVICE_DISCOVERY_MAX_ATTEMPTS,
934
1093
  targetUUIDs: ONEKEY_SERVICE_UUIDS,
935
1094
  });
936
1095
  if (attemptNumber > 1) {
937
- logger === null || logger === void 0 ? void 0 : logger.info(`[NobleBLE] Service discovery retry attempt ${attemptNumber}/5`);
1096
+ logger === null || logger === void 0 ? void 0 : logger.info(`[NobleBLE] Service discovery retry attempt ${attemptNumber}/${SERVICE_DISCOVERY_MAX_ATTEMPTS}`);
938
1097
  }
939
1098
  if (peripheral.state !== 'connected') {
940
1099
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Device not connected: ${peripheral.state}`);
@@ -943,18 +1102,19 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
943
1102
  return yield discoverServicesAndCharacteristics(peripheral);
944
1103
  }
945
1104
  catch (error) {
946
- logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] No services found (attempt ${attemptNumber}/5)`);
947
- if (attemptNumber < 5) {
948
- logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Will retry service discovery (attempt ${attemptNumber + 1}/5)`);
1105
+ logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] No services found (attempt ${attemptNumber}/${SERVICE_DISCOVERY_MAX_ATTEMPTS})`);
1106
+ if (attemptNumber < SERVICE_DISCOVERY_MAX_ATTEMPTS) {
1107
+ logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Will retry service discovery (attempt ${attemptNumber + 1}/${SERVICE_DISCOVERY_MAX_ATTEMPTS})`);
949
1108
  }
950
1109
  throw error;
951
1110
  }
952
1111
  }), {
953
- retries: 4,
1112
+ retries: SERVICE_DISCOVERY_MAX_ATTEMPTS - 1,
954
1113
  factor: 1.5,
955
- minTimeout: 1000,
1114
+ minTimeout: 500,
956
1115
  maxTimeout: 3000,
957
1116
  onFailedAttempt: error => {
1117
+ assertBleActive();
958
1118
  logger === null || logger === void 0 ? void 0 : logger.error(`[NobleBLE] Service discovery attempt ${error.attemptNumber} failed:`, {
959
1119
  message: error.message,
960
1120
  retriesLeft: error.retriesLeft,
@@ -966,23 +1126,29 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
966
1126
  }
967
1127
  function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
968
1128
  return index.__awaiter(this, void 0, void 0, function* () {
969
- setupDisconnectListener(peripheral, deviceId, webContents);
970
1129
  try {
971
- return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
1130
+ yield forceReconnectPeripheral(peripheral, deviceId);
972
1131
  }
973
- catch (initialError) {
974
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
975
- deviceId,
976
- error: initialError,
977
- });
1132
+ catch (resetError) {
1133
+ if (hdShared.isBleStaleBondHardwareError(resetError) ||
1134
+ (resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
1135
+ throw resetError;
1136
+ }
1137
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
978
1138
  }
979
- yield forceReconnectPeripheral(peripheral, deviceId);
1139
+ assertBleActive();
980
1140
  setupDisconnectListener(peripheral, deviceId, webContents);
981
1141
  try {
982
1142
  return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
983
1143
  }
984
- catch (reconnectError) {
985
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...', reconnectError);
1144
+ catch (discoveryError) {
1145
+ if (peripheral.state === 'connected') {
1146
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed on a live link, dropping it before the fresh scan', discoveryError);
1147
+ yield disconnectDevice(deviceId).catch(() => undefined);
1148
+ }
1149
+ else {
1150
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...', discoveryError);
1151
+ }
986
1152
  return freshScanAndDiscover(deviceId, webContents);
987
1153
  }
988
1154
  });
@@ -993,21 +1159,26 @@ const directConnectCooldownUntil = new Map();
993
1159
  function tryDirectConnectById(deviceId) {
994
1160
  var _a;
995
1161
  return index.__awaiter(this, void 0, void 0, function* () {
1162
+ assertBleActive();
996
1163
  const nobleInstance = noble;
997
1164
  if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
998
1165
  return undefined;
999
1166
  const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
1000
1167
  if (Date.now() < cooldownUntil)
1001
1168
  return undefined;
1169
+ let timer;
1170
+ let cancel;
1002
1171
  try {
1003
1172
  const directPromise = nobleInstance.connectAsync(deviceId);
1004
1173
  const raced = yield Promise.race([
1005
1174
  directPromise,
1006
1175
  new Promise(resolve => {
1007
- setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
1176
+ cancel = () => resolve('timeout');
1177
+ pendingCancellations.add(cancel);
1178
+ timer = setTimeout(cancel, DIRECT_CONNECT_TIMEOUT_MS);
1008
1179
  }),
1009
1180
  ]);
1010
- if (raced === 'timeout') {
1181
+ if (raced === 'timeout' || disposing) {
1011
1182
  directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
1012
1183
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
1013
1184
  deviceId,
@@ -1039,10 +1210,15 @@ function tryDirectConnectById(deviceId) {
1039
1210
  });
1040
1211
  return undefined;
1041
1212
  }
1213
+ finally {
1214
+ clearTimeout(timer);
1215
+ if (cancel)
1216
+ pendingCancellations.delete(cancel);
1217
+ }
1042
1218
  });
1043
1219
  }
1044
1220
  function connectDevice(deviceId, webContents) {
1045
- var _a;
1221
+ var _a, _b, _c;
1046
1222
  return index.__awaiter(this, void 0, void 0, function* () {
1047
1223
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
1048
1224
  deviceId,
@@ -1054,40 +1230,55 @@ function connectDevice(deviceId, webContents) {
1054
1230
  });
1055
1231
  let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
1056
1232
  if (!peripheral) {
1057
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
1058
1233
  if (!noble) {
1059
1234
  yield initializeNoble();
1060
1235
  }
1061
1236
  if (!noble) {
1062
1237
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
1063
1238
  }
1064
- peripheral = yield tryDirectConnectById(deviceId);
1065
- if (peripheral) {
1066
- discoveredDevices.set(deviceId, peripheral);
1067
- }
1068
- }
1069
- if (!peripheral) {
1070
- try {
1071
- const foundPeripheral = yield performTargetedScan(deviceId);
1072
- if (!foundPeripheral) {
1073
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found even after targeted scan`);
1239
+ const byIdFirst = shouldConnectByIdFirst(deviceId);
1240
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Resolving device for cold connect', {
1241
+ deviceId,
1242
+ name: (_b = resolveBleName(deviceId)) !== null && _b !== void 0 ? _b : 'unknown',
1243
+ strategy: byIdFirst ? 'connect-by-id-first' : 'scan-first',
1244
+ });
1245
+ const scanForPeripheral = () => index.__awaiter(this, void 0, void 0, function* () {
1246
+ var _d;
1247
+ try {
1248
+ return (_d = (yield performTargetedScan(deviceId, BLE_COLD_CONNECT_SCAN_TIMEOUT_MS))) !== null && _d !== void 0 ? _d : undefined;
1074
1249
  }
1075
- peripheral = foundPeripheral;
1076
- }
1077
- catch (error) {
1078
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Targeted scan failed:', error);
1079
- throw error;
1250
+ catch (scanError) {
1251
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan failed', {
1252
+ deviceId,
1253
+ error: scanError instanceof Error ? scanError.message : String(scanError),
1254
+ });
1255
+ return undefined;
1256
+ }
1257
+ });
1258
+ const connectById = () => index.__awaiter(this, void 0, void 0, function* () {
1259
+ const found = yield tryDirectConnectById(deviceId);
1260
+ if (found) {
1261
+ discoveredDevices.set(deviceId, found);
1262
+ }
1263
+ return found;
1264
+ });
1265
+ peripheral = byIdFirst ? yield connectById() : yield scanForPeripheral();
1266
+ if (!peripheral) {
1267
+ peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
1080
1268
  }
1081
1269
  }
1270
+ assertBleActive();
1082
1271
  if (!peripheral) {
1083
1272
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
1084
1273
  }
1274
+ rememberBleName(deviceId, (_c = peripheral.advertisement) === null || _c === void 0 ? void 0 : _c.localName);
1085
1275
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connecting to device:', deviceId);
1086
1276
  if (peripheral.state === 'connected') {
1087
1277
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
1088
1278
  if (!connectedDevices.has(deviceId)) {
1089
1279
  connectedDevices.set(deviceId, peripheral);
1090
1280
  }
1281
+ assertBleActive();
1091
1282
  setupDisconnectListener(peripheral, deviceId, webContents);
1092
1283
  if (deviceCharacteristics.has(deviceId)) {
1093
1284
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
@@ -1112,6 +1303,7 @@ function connectDevice(deviceId, webContents) {
1112
1303
  }
1113
1304
  try {
1114
1305
  const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
1306
+ assertBleActive();
1115
1307
  deviceCharacteristics.set(deviceId, characteristics);
1116
1308
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1117
1309
  }
@@ -1122,65 +1314,46 @@ function connectDevice(deviceId, webContents) {
1122
1314
  }
1123
1315
  return;
1124
1316
  }
1125
- return new Promise((resolve, reject) => {
1126
- let connectionTimedOut = false;
1127
- const timeout = setTimeout(() => {
1128
- connectionTimedOut = true;
1129
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1130
- }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
1131
- const connectedPeripheral = peripheral;
1132
- connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
1133
- clearTimeout(timeout);
1134
- if (connectionTimedOut) {
1135
- if (!error) {
1136
- try {
1137
- connectedPeripheral.disconnect(() => undefined);
1138
- }
1139
- catch (_a) {
1140
- }
1141
- }
1142
- return;
1143
- }
1144
- if (error) {
1145
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
1146
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
1147
- return;
1148
- }
1149
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
1150
- connectedDevices.set(deviceId, connectedPeripheral);
1151
- try {
1152
- const characteristics = yield setupConnectionAndDiscoverServices(connectedPeripheral, deviceId, webContents);
1153
- deviceCharacteristics.set(deviceId, characteristics);
1154
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1155
- resolve();
1156
- }
1157
- catch (setupError) {
1158
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
1159
- connectedPeripheral.disconnect(() => {
1160
- reject(setupError);
1161
- });
1162
- }
1163
- }));
1164
- });
1317
+ const connectedPeripheral = peripheral;
1318
+ yield connectPeripheralWithCancellation(connectedPeripheral, deviceId);
1319
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
1320
+ try {
1321
+ const characteristics = yield setupConnectionAndDiscoverServices(connectedPeripheral, deviceId, webContents);
1322
+ assertBleActive();
1323
+ deviceCharacteristics.set(deviceId, characteristics);
1324
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1325
+ }
1326
+ catch (setupError) {
1327
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed:', setupError);
1328
+ yield disconnectDevice(deviceId).catch(() => undefined);
1329
+ throw setupError;
1330
+ }
1165
1331
  });
1166
1332
  }
1167
1333
  function disconnectDevice(deviceId) {
1334
+ var _a;
1168
1335
  return index.__awaiter(this, void 0, void 0, function* () {
1169
- const peripheral = connectedDevices.get(deviceId);
1336
+ const pendingConnection = connectingDevices.get(deviceId);
1337
+ const peripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.peripheral;
1170
1338
  if (!peripheral) {
1171
1339
  return;
1172
1340
  }
1341
+ pendingConnection === null || pendingConnection === void 0 ? void 0 : pendingConnection.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, `Device ${deviceId} connection cancelled`));
1173
1342
  const disconnectEntry = deviceDisconnectListeners.get(deviceId);
1174
1343
  if (disconnectEntry) {
1175
1344
  disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
1176
1345
  deviceDisconnectListeners.delete(deviceId);
1177
1346
  }
1178
1347
  yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
1179
- timeoutMs: BLE_CLEANUP_TIMEOUT,
1348
+ timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
1180
1349
  timeoutBehavior: 'resolve',
1181
1350
  });
1351
+ if (peripheral.state === 'connected') {
1352
+ logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Physical disconnect did not confirm in time', { deviceId });
1353
+ }
1182
1354
  cleanupDevice(deviceId, undefined, {
1183
1355
  cleanupConnection: true,
1356
+ cleanupDiscoveredCache: true,
1184
1357
  sendDisconnectEvent: false,
1185
1358
  cancelOperations: true,
1186
1359
  reason: 'manual-disconnect',
@@ -1208,7 +1381,8 @@ function unsubscribeNotifications(deviceId) {
1208
1381
  subscribedDevices.delete(deviceId);
1209
1382
  }
1210
1383
  finally {
1211
- subscriptionOperations.set(deviceId, 'idle');
1384
+ if (!disposing)
1385
+ subscriptionOperations.set(deviceId, 'idle');
1212
1386
  }
1213
1387
  });
1214
1388
  }
@@ -1258,10 +1432,12 @@ function subscribeNotifications(deviceId, callback) {
1258
1432
  timeoutMs: BLE_CLEANUP_TIMEOUT,
1259
1433
  timeoutBehavior: 'resolve',
1260
1434
  });
1435
+ assertBleActive();
1261
1436
  yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1262
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1437
+ timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1263
1438
  timeoutBehavior: 'reject',
1264
1439
  });
1440
+ assertBleActive();
1265
1441
  notifyCharacteristic.on('data', (data) => {
1266
1442
  if (!pairedDevices.has(deviceId)) {
1267
1443
  pairedDevices.add(deviceId);
@@ -1271,22 +1447,45 @@ function subscribeNotifications(deviceId, callback) {
1271
1447
  });
1272
1448
  });
1273
1449
  }
1450
+ const subscribeStartedAt = Date.now();
1274
1451
  try {
1275
1452
  yield rebuildAppSubscription(deviceId, notifyCharacteristic);
1453
+ assertBleActive();
1276
1454
  subscribedDevices.set(deviceId, true);
1455
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification subscription active', {
1456
+ deviceId,
1457
+ ms: Date.now() - subscribeStartedAt,
1458
+ });
1459
+ }
1460
+ catch (error) {
1461
+ throw createNobleBleConnectionError(error, 'Notification subscription failed: ');
1277
1462
  }
1278
1463
  finally {
1279
- subscriptionOperations.set(deviceId, 'idle');
1464
+ if (!disposing)
1465
+ subscriptionOperations.set(deviceId, 'idle');
1280
1466
  }
1281
1467
  });
1282
1468
  }
1283
1469
  function setupNobleBleHandlers(webContents) {
1470
+ if (disposing)
1471
+ return;
1284
1472
  try {
1285
1473
  logger = require('electron-log');
1286
1474
  const { ipcMain } = require('electron');
1475
+ const channels = new Set();
1476
+ removeIpcHandlers = () => channels.forEach(channel => ipcMain.removeHandler(channel));
1287
1477
  const handle = (channel, listener) => {
1478
+ channels.add(channel);
1288
1479
  ipcMain.removeHandler(channel);
1289
- ipcMain.handle(channel, listener);
1480
+ ipcMain.handle(channel, (...args) => index.__awaiter(this, void 0, void 0, function* () {
1481
+ try {
1482
+ assertBleActive();
1483
+ return yield Promise.resolve(listener(...args));
1484
+ }
1485
+ catch (error) {
1486
+ return createNobleBleIpcErrorResponse(error);
1487
+ }
1488
+ }));
1290
1489
  };
1291
1490
  safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
1292
1491
  handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
@@ -1383,6 +1582,8 @@ function setupNobleBleHandlers(webContents) {
1383
1582
  }
1384
1583
  }));
1385
1584
  webContents.on('destroyed', () => {
1585
+ if (disposing)
1586
+ return;
1386
1587
  safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1387
1588
  (() => index.__awaiter(this, void 0, void 0, function* () {
1388
1589
  const deviceIds = Array.from(connectedDevices.keys());
@@ -1391,10 +1592,6 @@ function setupNobleBleHandlers(webContents) {
1391
1592
  yield disconnectDevice(deviceId).catch(() => undefined);
1392
1593
  }
1393
1594
  yield stopScanning().catch(() => undefined);
1394
- if (noble && persistentStateListener) {
1395
- noble.removeListener('stateChange', persistentStateListener);
1396
- persistentStateListener = null;
1397
- }
1398
1595
  cleanupNobleListeners();
1399
1596
  discoveredDevices.clear();
1400
1597
  safeLog(logger, 'info', 'Noble BLE cleanup completed');
@@ -1409,6 +1606,65 @@ function setupNobleBleHandlers(webContents) {
1409
1606
  throw error;
1410
1607
  }
1411
1608
  }
1609
+ function disposeNobleBleSupport(releaseNoble = instance => instance.stop()) {
1610
+ if (disposePromise)
1611
+ return disposePromise;
1612
+ disposing = true;
1613
+ removeIpcHandlers === null || removeIpcHandlers === void 0 ? void 0 : removeIpcHandlers();
1614
+ const instance = noble;
1615
+ const deviceIds = new Set([...connectedDevices.keys(), ...connectingDevices.keys()]);
1616
+ const pendingPeripherals = Array.from(connectingDevices.values(), pending => pending.peripheral);
1617
+ for (const cancel of pendingCancellations)
1618
+ cancel();
1619
+ for (const id of idleDisconnectTimers.keys())
1620
+ clearIdleDisconnect(id);
1621
+ for (const pending of connectingDevices.values()) {
1622
+ pending.cancel(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, 'Noble BLE is shutting down'));
1623
+ }
1624
+ let timeout;
1625
+ disposePromise = (() => index.__awaiter(this, void 0, void 0, function* () {
1626
+ try {
1627
+ yield Promise.race([
1628
+ Promise.allSettled([
1629
+ stopScanning(),
1630
+ ...pendingPeripherals.map(peripheral => runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
1631
+ timeoutMs: BLE_DISCONNECT_CONFIRM_TIMEOUT_MS,
1632
+ timeoutBehavior: 'resolve',
1633
+ })),
1634
+ ...Array.from(deviceIds, (id) => index.__awaiter(this, void 0, void 0, function* () {
1635
+ yield unsubscribeNotifications(id).catch(() => undefined);
1636
+ yield disconnectDevice(id).catch(() => undefined);
1637
+ })),
1638
+ ]),
1639
+ new Promise(resolve => {
1640
+ timeout = setTimeout(() => {
1641
+ logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Process dispose timed out; releasing native manager');
1642
+ resolve();
1643
+ }, 3500);
1644
+ }),
1645
+ ]);
1646
+ }
1647
+ finally {
1648
+ clearTimeout(timeout);
1649
+ cleanupNobleListeners();
1650
+ if (instance && persistentStateListener) {
1651
+ instance.removeListener('stateChange', persistentStateListener);
1652
+ persistentStateListener = null;
1653
+ }
1654
+ for (const id of deviceIds)
1655
+ cleanupDevice(id);
1656
+ discoveredDevices.clear();
1657
+ directConnectCooldownUntil.clear();
1658
+ if (instance)
1659
+ releaseNoble(instance);
1660
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Process dispose completed');
1661
+ }
1662
+ }))();
1663
+ return disposePromise;
1664
+ }
1412
1665
 
1666
+ exports.createNobleBleConnectionError = createNobleBleConnectionError;
1667
+ exports.createNobleBleIpcErrorResponse = createNobleBleIpcErrorResponse;
1668
+ exports.disposeNobleBleSupport = disposeNobleBleSupport;
1413
1669
  exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
1414
1670
  exports.setupNobleBleHandlers = setupNobleBleHandlers;