@onekeyfe/hd-transport-electron 1.2.0-alpha.8 → 1.2.0-alpha.80

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-02fc07fe.js');
3
+ var index = require('./index-6da32f94.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -8,6 +8,14 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  var pRetry__default = /*#__PURE__*/_interopDefaultLegacy(pRetry);
10
10
 
11
+ const BLE_ATT_HEADER_BYTES = 3;
12
+ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
13
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
14
+ return fallbackPacketCapacity;
15
+ }
16
+ return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
17
+ }
18
+
11
19
  function safeLog(logger, level, message, ...args) {
12
20
  if (logger) {
13
21
  logger[level](message, ...args);
@@ -17,6 +25,37 @@ function safeLog(logger, level, message, ...args) {
17
25
  }
18
26
  }
19
27
 
28
+ function runBleCallbackOperation(operation, options) {
29
+ return new Promise((resolve, reject) => {
30
+ let settled = false;
31
+ const settle = (error) => {
32
+ if (settled)
33
+ return;
34
+ settled = true;
35
+ clearTimeout(timeout);
36
+ if (error) {
37
+ reject(error);
38
+ }
39
+ else {
40
+ resolve();
41
+ }
42
+ };
43
+ const timeout = setTimeout(() => {
44
+ if (options.timeoutBehavior === 'reject') {
45
+ settle(new Error(`BLE operation timed out after ${options.timeoutMs}ms`));
46
+ }
47
+ else {
48
+ settle();
49
+ }
50
+ }, options.timeoutMs);
51
+ try {
52
+ operation(settle);
53
+ }
54
+ catch (error) {
55
+ settle(error instanceof Error ? error : new Error(String(error)));
56
+ }
57
+ });
58
+ }
20
59
  function softRefreshSubscription(params) {
21
60
  return index.__awaiter(this, void 0, void 0, function* () {
22
61
  const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices, onNotificationData, logger, } = params;
@@ -25,17 +64,13 @@ function softRefreshSubscription(params) {
25
64
  }
26
65
  logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Starting subscription refresh', { deviceId });
27
66
  subscriptionOperations.set(deviceId, 'subscribing');
28
- yield new Promise(resolve => {
29
- notifyCharacteristic.unsubscribe(() => resolve());
67
+ yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
68
+ timeoutMs: 250,
69
+ timeoutBehavior: 'resolve',
30
70
  });
31
- yield new Promise((resolve, reject) => {
32
- notifyCharacteristic.subscribe((error) => {
33
- if (error) {
34
- reject(error);
35
- return;
36
- }
37
- resolve();
38
- });
71
+ yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
72
+ timeoutMs: 8000,
73
+ timeoutBehavior: 'reject',
39
74
  });
40
75
  notifyCharacteristic.removeAllListeners('data');
41
76
  notifyCharacteristic.on('data', (data) => {
@@ -51,6 +86,9 @@ function softRefreshSubscription(params) {
51
86
  });
52
87
  }
53
88
 
89
+ const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
90
+ const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
91
+
54
92
  let noble = null;
55
93
  let logger = null;
56
94
  const bluetoothState = {
@@ -59,6 +97,7 @@ const bluetoothState = {
59
97
  initialized: false,
60
98
  };
61
99
  let persistentStateListener = null;
100
+ let persistentDiscoverListener = null;
62
101
  const discoveredDevices = new Map();
63
102
  const connectedDevices = new Map();
64
103
  const pairedDevices = new Set();
@@ -66,54 +105,47 @@ const deviceCharacteristics = new Map();
66
105
  const notificationCallbacks = new Map();
67
106
  const subscribedDevices = new Map();
68
107
  const subscriptionOperations = new Map();
108
+ const deviceDisconnectListeners = new Map();
109
+ const deviceMtuListeners = new Map();
69
110
  const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
70
- const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
71
- const NORMALIZED_WRITE_UUID = '0002';
72
- const NORMALIZED_NOTIFY_UUID = '0003';
111
+ const ONEKEY_SERVICE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_SERVICE_UUID);
112
+ const ONEKEY_WRITE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID);
113
+ const ONEKEY_NOTIFY_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID);
73
114
  const BLUETOOTH_INIT_TIMEOUT = 10000;
74
- const DEVICE_SCAN_TIMEOUT = 8000;
75
- const FAST_SCAN_TIMEOUT = 8000;
115
+ const DEVICE_SCAN_TIMEOUT = 5000;
76
116
  const DEVICE_CHECK_INTERVAL = 500;
77
- const CONNECTION_TIMEOUT = 8000;
78
117
  const SERVICE_DISCOVERY_TIMEOUT = 10000;
79
- const BLE_PACKET_SIZE = 192;
80
- const UNIFIED_WRITE_DELAY = 5;
118
+ const BLE_CLEANUP_TIMEOUT = 250;
119
+ const BLE_IDLE_DISCONNECT_MS = 3 * 60000;
120
+ const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
121
+ const BLE_PACKET_SIZE_FALLBACK = 192;
122
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
123
+ const DEFAULT_WRITE_PACING_DELAY_MS = 5;
81
124
  const RETRY_CONFIG = { MAX_ATTEMPTS: 15, WRITE_TIMEOUT: 2000 };
82
125
  const IS_WINDOWS = process.platform === 'win32';
83
126
  const ABORTABLE_WRITE_ERROR_PATTERNS = [
84
127
  /status:\s*3/i,
85
128
  ];
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
- });
129
+ function resolveNobleBleWritePacingDelay(options) {
130
+ return typeof (options === null || options === void 0 ? void 0 : options.pacingDelayMs) === 'number' && Number.isFinite(options.pacingDelayMs)
131
+ ? Math.min(Math.max(Math.floor(options.pacingDelayMs), 0), 1000)
132
+ : DEFAULT_WRITE_PACING_DELAY_MS;
105
133
  }
106
134
  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);
135
+ var _a, _b;
136
+ const serviceUuids = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids;
137
+ const localName = (_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName;
138
+ if (!(localName === null || localName === void 0 ? void 0 : localName.trim()) || hdShared.isPro2FindMyAdvertisementName(localName)) {
139
+ return false;
140
+ }
141
+ return (hdShared.hasOnekeyCommunicationService(serviceUuids) &&
142
+ hdShared.isOnekeyBluetoothDevice({
143
+ id: peripheral.id,
144
+ localName,
145
+ serviceUuids,
146
+ }));
110
147
  }
111
148
  function emitRawNotification(deviceId, data) {
112
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Raw notification', {
113
- deviceId,
114
- dataLength: data.length,
115
- firstBytes: data.subarray(0, 8).toString('hex'),
116
- });
117
149
  const appCb = notificationCallbacks.get(deviceId);
118
150
  if (appCb)
119
151
  appCb(data.toString('hex'));
@@ -250,9 +282,12 @@ function initializeNoble() {
250
282
  };
251
283
  noble.on('stateChange', onStateChange);
252
284
  });
253
- noble.on('discover', (peripheral) => {
254
- handleDeviceDiscovered(peripheral);
255
- });
285
+ if (!persistentDiscoverListener) {
286
+ persistentDiscoverListener = (peripheral) => {
287
+ handleDeviceDiscovered(peripheral);
288
+ };
289
+ noble.on('discover', persistentDiscoverListener);
290
+ }
256
291
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble initialized successfully');
257
292
  }
258
293
  catch (error) {
@@ -263,6 +298,62 @@ function initializeNoble() {
263
298
  }
264
299
  });
265
300
  }
301
+ const idleDisconnectTimers = new Map();
302
+ const idleDisconnectInFlight = new Map();
303
+ function clearIdleDisconnect(deviceId) {
304
+ const timer = idleDisconnectTimers.get(deviceId);
305
+ if (timer) {
306
+ clearTimeout(timer);
307
+ idleDisconnectTimers.delete(deviceId);
308
+ }
309
+ }
310
+ function awaitIdleDisconnect(deviceId) {
311
+ return index.__awaiter(this, void 0, void 0, function* () {
312
+ const pending = idleDisconnectInFlight.get(deviceId);
313
+ if (pending)
314
+ yield pending.catch(() => undefined);
315
+ });
316
+ }
317
+ function broadcastToAllWebContents(channel, payload) {
318
+ try {
319
+ const { webContents: electronWebContents } = require('electron');
320
+ for (const wc of electronWebContents.getAllWebContents()) {
321
+ if (!wc.isDestroyed())
322
+ wc.send(channel, payload);
323
+ }
324
+ }
325
+ catch (error) {
326
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
327
+ }
328
+ }
329
+ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
330
+ clearIdleDisconnect(deviceId);
331
+ idleDisconnectTimers.set(deviceId, setTimeout(() => {
332
+ var _a;
333
+ idleDisconnectTimers.delete(deviceId);
334
+ if (!connectedDevices.has(deviceId))
335
+ return;
336
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device', { deviceId, reason });
337
+ const peripheral = connectedDevices.get(deviceId);
338
+ 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)
340
+ .then(() => {
341
+ broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
342
+ id: deviceId,
343
+ name: deviceName,
344
+ });
345
+ })
346
+ .catch(error => {
347
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Keep-alive disconnect failed', { deviceId, error });
348
+ })
349
+ .finally(() => {
350
+ if (idleDisconnectInFlight.get(deviceId) === pending) {
351
+ idleDisconnectInFlight.delete(deviceId);
352
+ }
353
+ });
354
+ idleDisconnectInFlight.set(deviceId, pending);
355
+ }, ms));
356
+ }
266
357
  function cleanupDevice(deviceId, webContents, options = {}) {
267
358
  var _a;
268
359
  const { cleanupConnection = true, cleanupDiscoveredCache = false, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', } = options;
@@ -274,9 +365,20 @@ function cleanupDevice(deviceId, webContents, options = {}) {
274
365
  sendDisconnectEvent,
275
366
  cancelOperations,
276
367
  });
368
+ clearIdleDisconnect(deviceId);
277
369
  const peripheral = connectedDevices.get(deviceId);
278
370
  const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
279
371
  if (cleanupConnection) {
372
+ const disconnectEntry = deviceDisconnectListeners.get(deviceId);
373
+ if (disconnectEntry) {
374
+ disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
375
+ deviceDisconnectListeners.delete(deviceId);
376
+ }
377
+ const mtuEntry = deviceMtuListeners.get(deviceId);
378
+ if (mtuEntry) {
379
+ mtuEntry.peripheral.removeListener('mtu', mtuEntry.listener);
380
+ deviceMtuListeners.delete(deviceId);
381
+ }
280
382
  connectedDevices.delete(deviceId);
281
383
  deviceCharacteristics.delete(deviceId);
282
384
  notificationCallbacks.delete(deviceId);
@@ -287,8 +389,8 @@ function cleanupDevice(deviceId, webContents, options = {}) {
287
389
  if (cleanupDiscoveredCache) {
288
390
  discoveredDevices.delete(deviceId);
289
391
  }
290
- if (sendDisconnectEvent && webContents) {
291
- webContents.send(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
392
+ if (sendDisconnectEvent) {
393
+ broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
292
394
  id: deviceId,
293
395
  name: deviceName,
294
396
  });
@@ -311,12 +413,36 @@ function handleDeviceDisconnect(deviceId, webContents) {
311
413
  });
312
414
  }
313
415
  function setupDisconnectListener(peripheral, deviceId, webContents) {
314
- peripheral.removeAllListeners('disconnect');
315
- peripheral.on('disconnect', () => {
416
+ const existing = deviceDisconnectListeners.get(deviceId);
417
+ if (existing) {
418
+ existing.peripheral.removeListener('disconnect', existing.listener);
419
+ }
420
+ const listener = () => {
316
421
  handleDeviceDisconnect(deviceId, webContents);
317
- });
422
+ };
423
+ deviceDisconnectListeners.set(deviceId, { peripheral, listener });
424
+ peripheral.on('disconnect', listener);
425
+ setupMtuListener(peripheral, deviceId, webContents);
426
+ }
427
+ function setupMtuListener(peripheral, deviceId, webContents) {
428
+ const existing = deviceMtuListeners.get(deviceId);
429
+ if (existing) {
430
+ existing.peripheral.removeListener('mtu', existing.listener);
431
+ }
432
+ const listener = (mtu) => {
433
+ if (!Number.isFinite(mtu) || mtu <= 0)
434
+ return;
435
+ if (webContents.isDestroyed())
436
+ return;
437
+ webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
438
+ id: deviceId,
439
+ mtu,
440
+ });
441
+ };
442
+ deviceMtuListeners.set(deviceId, { peripheral, listener });
443
+ peripheral.on('mtu', listener);
318
444
  }
319
- function writeCharacteristicWithAck(deviceId, writeCharacteristic, buffer) {
445
+ function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
320
446
  return index.__awaiter(this, void 0, void 0, function* () {
321
447
  return new Promise((resolve, reject) => {
322
448
  writeCharacteristic.write(buffer, true, (error) => {
@@ -348,7 +474,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
348
474
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Write characteristic not available for ${deviceId}`);
349
475
  }
350
476
  try {
351
- yield writeCharacteristicWithAck(deviceId, latestWrite, payload);
477
+ yield writeCharacteristicWithoutResponse(deviceId, latestWrite, payload);
352
478
  }
353
479
  catch (e) {
354
480
  const errorMessage = e instanceof Error ? e.message : String(e);
@@ -407,92 +533,98 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
407
533
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `No response observed after ${RETRY_CONFIG.MAX_ATTEMPTS} writes: ${deviceId}`);
408
534
  });
409
535
  }
410
- function transmitHexDataToDevice(deviceId, hexData) {
536
+ function transmitHexDataToDevice(deviceId, hexData, options) {
411
537
  return index.__awaiter(this, void 0, void 0, function* () {
412
538
  const characteristics = deviceCharacteristics.get(deviceId);
413
539
  const peripheral = connectedDevices.get(deviceId);
414
540
  if (!peripheral || !characteristics) {
415
541
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
416
542
  }
543
+ armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
417
544
  const toBuffer = Buffer.from(hexData, 'hex');
418
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Writing data:', {
419
- deviceId,
420
- dataLength: toBuffer.length,
421
- firstBytes: toBuffer.subarray(0, 8).toString('hex'),
422
- });
423
545
  const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
546
+ const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
547
+ const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
424
548
  if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
425
549
  const writeCharacteristic = doGetWriteCharacteristic();
426
550
  if (!writeCharacteristic) {
427
551
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
428
552
  }
429
- if (toBuffer.length <= BLE_PACKET_SIZE) {
430
- yield hdShared.wait(UNIFIED_WRITE_DELAY);
431
- yield writeCharacteristicWithAck(deviceId, writeCharacteristic, toBuffer);
553
+ if (toBuffer.length <= packetCapacity) {
554
+ if (pacingDelayMs > 0)
555
+ yield hdShared.wait(pacingDelayMs);
556
+ yield writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, toBuffer);
432
557
  return;
433
558
  }
434
559
  for (let offset = 0; offset < toBuffer.length;) {
435
- const chunkSize = Math.min(BLE_PACKET_SIZE, toBuffer.length - offset);
560
+ const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
436
561
  const chunk = toBuffer.subarray(offset, offset + chunkSize);
437
562
  offset += chunkSize;
438
563
  const latest = doGetWriteCharacteristic();
439
564
  if (!latest) {
440
565
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
441
566
  }
442
- yield writeCharacteristicWithAck(deviceId, latest, chunk);
443
- if (offset < toBuffer.length) {
444
- yield hdShared.wait(UNIFIED_WRITE_DELAY);
567
+ yield writeCharacteristicWithoutResponse(deviceId, latest, chunk);
568
+ if (offset < toBuffer.length && pacingDelayMs > 0) {
569
+ yield hdShared.wait(pacingDelayMs);
445
570
  }
446
571
  }
447
572
  return;
448
573
  }
449
- if (toBuffer.length <= BLE_PACKET_SIZE) {
450
- yield hdShared.wait(UNIFIED_WRITE_DELAY);
574
+ if (toBuffer.length <= packetCapacity) {
575
+ if (pacingDelayMs > 0)
576
+ yield hdShared.wait(pacingDelayMs);
451
577
  yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
452
578
  return;
453
579
  }
454
580
  for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
455
- const chunkSize = Math.min(BLE_PACKET_SIZE, toBuffer.length - offset);
581
+ const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
456
582
  const chunk = toBuffer.subarray(offset, offset + chunkSize);
457
583
  offset += chunkSize;
458
584
  yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, chunk, `chunk-${idx + 1}`);
459
- if (offset < toBuffer.length) {
460
- yield hdShared.wait(UNIFIED_WRITE_DELAY);
585
+ if (offset < toBuffer.length && pacingDelayMs > 0) {
586
+ yield hdShared.wait(pacingDelayMs);
461
587
  }
462
588
  }
463
589
  });
464
590
  }
465
591
  function handleDeviceDiscovered(peripheral) {
466
592
  var _a, _b;
467
- 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
593
  if (!isOneKeyPeripheral(peripheral)) {
470
594
  return;
471
595
  }
472
596
  const isNewDevice = !discoveredDevices.has(peripheral.id);
473
597
  discoveredDevices.set(peripheral.id, peripheral);
474
598
  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,
599
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] OneKey BLE device discovered', {
600
+ deviceId: peripheral.id,
601
+ name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
602
+ serviceUUIDs: ((_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.serviceUuids) || [],
479
603
  });
480
604
  }
481
605
  }
482
606
  function ensureDiscoverListener() {
483
607
  if (!noble)
484
608
  return;
485
- const listenerCount = noble.listenerCount('discover');
486
- if (listenerCount === 0) {
609
+ if (!persistentDiscoverListener) {
487
610
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discover listener missing, re-adding it');
488
- noble.on('discover', (peripheral) => {
611
+ persistentDiscoverListener = (peripheral) => {
489
612
  handleDeviceDiscovered(peripheral);
490
- });
613
+ };
614
+ noble.on('discover', persistentDiscoverListener);
491
615
  }
492
616
  else {
493
- logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already exists, count:', listenerCount);
617
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already registered');
494
618
  }
495
619
  }
620
+ function waitForNobleScanStop(nobleInstance) {
621
+ return index.__awaiter(this, void 0, void 0, function* () {
622
+ yield runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
623
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
624
+ timeoutBehavior: 'resolve',
625
+ });
626
+ });
627
+ }
496
628
  function performTargetedScan(targetDeviceId) {
497
629
  return index.__awaiter(this, void 0, void 0, function* () {
498
630
  if (!noble) {
@@ -501,33 +633,43 @@ function performTargetedScan(targetDeviceId) {
501
633
  const nobleInstance = noble;
502
634
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
503
635
  return new Promise((resolve, reject) => {
636
+ let settled = false;
637
+ const finish = (peripheral, error) => index.__awaiter(this, void 0, void 0, function* () {
638
+ if (settled)
639
+ return;
640
+ settled = true;
641
+ if (timeoutId)
642
+ clearTimeout(timeoutId);
643
+ nobleInstance.removeListener('discover', onDiscover);
644
+ yield waitForNobleScanStop(nobleInstance);
645
+ if (error) {
646
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
647
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
648
+ return;
649
+ }
650
+ if (peripheral) {
651
+ discoveredDevices.set(peripheral.id, peripheral);
652
+ }
653
+ resolve(peripheral);
654
+ });
504
655
  const onDiscover = (peripheral) => {
505
656
  var _a;
506
- if (peripheral.id === targetDeviceId) {
657
+ if (peripheral.id === targetDeviceId && isOneKeyPeripheral(peripheral)) {
507
658
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Target device found during targeted scan:', {
508
659
  id: peripheral.id,
509
660
  name: (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName,
510
661
  });
511
- clearTimeout(timeoutId);
512
- nobleInstance.removeListener('discover', onDiscover);
513
- nobleInstance.stopScanning();
514
- discoveredDevices.set(peripheral.id, peripheral);
515
- resolve(peripheral);
662
+ finish(peripheral).catch(reject);
516
663
  }
517
664
  };
518
665
  const timeoutId = setTimeout(() => {
519
- nobleInstance.removeListener('discover', onDiscover);
520
- nobleInstance.stopScanning();
521
666
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
522
- resolve(null);
523
- }, FAST_SCAN_TIMEOUT);
667
+ finish(null).catch(reject);
668
+ }, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
524
669
  nobleInstance.on('discover', onDiscover);
525
670
  nobleInstance.startScanning([], false, (error) => {
526
671
  if (error) {
527
- clearTimeout(timeoutId);
528
- nobleInstance.removeListener('discover', onDiscover);
529
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
530
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
672
+ finish(null, error).catch(reject);
531
673
  return;
532
674
  }
533
675
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
@@ -550,44 +692,44 @@ function enumerateDevices() {
550
692
  return new Promise((resolve, reject) => {
551
693
  const devices = [];
552
694
  let intervalId;
553
- const cleanup = () => {
695
+ const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
554
696
  clearTimeout(timeoutId);
555
697
  if (intervalId)
556
698
  clearInterval(intervalId);
557
- nobleInstance.stopScanning();
699
+ yield waitForNobleScanStop(nobleInstance);
700
+ });
701
+ const pushDevice = (peripheral, id) => {
702
+ var _a;
703
+ if (devices.some(d => d.id === id))
704
+ return;
705
+ devices.push({
706
+ commType: 'electron-ble',
707
+ id,
708
+ name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
709
+ state: peripheral.state || 'disconnected',
710
+ });
558
711
  };
559
712
  const checkDevices = () => {
560
- discoveredDevices.forEach((peripheral, id) => {
561
- var _a;
562
- const existingDevice = devices.find(d => d.id === id);
563
- if (!existingDevice) {
564
- const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
565
- devices.push({
566
- commType: 'electron-ble',
567
- id,
568
- name: deviceName,
569
- state: peripheral.state || 'disconnected',
570
- });
571
- }
572
- });
713
+ discoveredDevices.forEach(pushDevice);
714
+ connectedDevices.forEach(pushDevice);
573
715
  };
574
- const timeoutId = setTimeout(() => {
716
+ const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
575
717
  checkDevices();
576
- cleanup();
718
+ yield cleanup();
577
719
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
578
720
  resolve(devices);
579
- }, DEVICE_SCAN_TIMEOUT);
721
+ }), DEVICE_SCAN_TIMEOUT);
580
722
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
581
- nobleInstance.startScanning([], false, (error) => {
723
+ nobleInstance.startScanning([], false, (error) => index.__awaiter(this, void 0, void 0, function* () {
582
724
  if (error) {
583
- cleanup();
725
+ yield cleanup();
584
726
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
585
727
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
586
728
  return;
587
729
  }
588
730
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning started for OneKey devices');
589
731
  intervalId = setInterval(checkDevices, DEVICE_CHECK_INTERVAL);
590
- });
732
+ }));
591
733
  });
592
734
  });
593
735
  }
@@ -595,25 +737,20 @@ function stopScanning() {
595
737
  return index.__awaiter(this, void 0, void 0, function* () {
596
738
  if (!noble)
597
739
  return;
598
- return new Promise(resolve => {
599
- if (!noble) {
600
- resolve();
601
- return;
602
- }
603
- noble.stopScanning(() => {
604
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
605
- resolve();
606
- });
607
- });
740
+ const nobleInstance = noble;
741
+ yield waitForNobleScanStop(nobleInstance);
742
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
608
743
  });
609
744
  }
610
745
  function cleanupNobleListeners() {
611
746
  if (!noble)
612
747
  return;
613
748
  try {
614
- noble.removeAllListeners('discover');
615
- noble.removeAllListeners('stateChange');
616
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] All Noble listeners cleaned up');
749
+ if (persistentDiscoverListener) {
750
+ noble.removeListener('discover', persistentDiscoverListener);
751
+ persistentDiscoverListener = null;
752
+ }
753
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Owned Noble listeners cleaned up');
617
754
  }
618
755
  catch (error) {
619
756
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to clean up some listeners:', error);
@@ -624,22 +761,12 @@ function getDevice(deviceId) {
624
761
  const peripheral = discoveredDevices.get(deviceId);
625
762
  if (peripheral) {
626
763
  const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
627
- return {
628
- commType: 'electron-ble',
629
- id: peripheral.id,
630
- name: deviceName,
631
- state: peripheral.state || 'disconnected',
632
- };
764
+ return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}));
633
765
  }
634
766
  const connectedPeripheral = connectedDevices.get(deviceId);
635
767
  if (connectedPeripheral) {
636
768
  const deviceName = ((_b = connectedPeripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device';
637
- return {
638
- commType: 'electron-ble',
639
- id: connectedPeripheral.id,
640
- name: deviceName,
641
- state: connectedPeripheral.state || 'connected',
642
- };
769
+ return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}));
643
770
  }
644
771
  return {
645
772
  commType: 'electron-ble',
@@ -683,23 +810,22 @@ function discoverServicesAndCharacteristics(peripheral) {
683
810
  }
684
811
  });
685
812
  });
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
813
  if (!services || services.length === 0) {
688
814
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No services found');
689
815
  }
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
- }
816
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] services discovered', {
817
+ deviceId: peripheral.id,
818
+ serviceUUIDs: services.map(service => service.uuid),
819
+ });
820
+ const service = services.find(s => hdShared.matchesKnownBleUuid(s.uuid, ONEKEY_SERVICE_UUID_ALIASES));
698
821
  if (!service) {
699
822
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
700
823
  }
701
824
  const selectedService = service;
702
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Using service:', selectedService.uuid);
825
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] service selected', {
826
+ deviceId: peripheral.id,
827
+ serviceUuid: selectedService.uuid,
828
+ });
703
829
  const characteristics = yield new Promise((resolve, reject) => {
704
830
  selectedService.discoverCharacteristics([], (error, chars) => {
705
831
  if (error) {
@@ -711,30 +837,26 @@ function discoverServicesAndCharacteristics(peripheral) {
711
837
  }
712
838
  });
713
839
  });
714
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discovered characteristics:', {
715
- count: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.length) || 0,
716
- uuids: (characteristics === null || characteristics === void 0 ? void 0 : characteristics.map(c => c.uuid)) || [],
717
- });
718
840
  let writeCharacteristic = null;
719
841
  let notifyCharacteristic = null;
720
842
  for (const characteristic of characteristics) {
721
- const uuid = characteristic.uuid.replace(/-/g, '').toLowerCase();
722
- const uuidKey = uuid.length >= 8 ? uuid.substring(4, 8) : uuid;
723
- if (uuidKey === NORMALIZED_WRITE_UUID) {
843
+ if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_WRITE_UUID_ALIASES)) {
724
844
  writeCharacteristic = characteristic;
725
845
  }
726
- else if (uuidKey === NORMALIZED_NOTIFY_UUID) {
846
+ else if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_NOTIFY_UUID_ALIASES)) {
727
847
  notifyCharacteristic = characteristic;
728
848
  }
729
849
  }
730
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Characteristic discovery result:', {
731
- writeFound: !!writeCharacteristic,
732
- notifyFound: !!notifyCharacteristic,
733
- });
734
850
  if (!writeCharacteristic || !notifyCharacteristic) {
735
851
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Missing characteristics - write:', !!writeCharacteristic, 'notify:', !!notifyCharacteristic);
736
852
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, 'Required characteristics not found');
737
853
  }
854
+ logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] characteristics selected', {
855
+ deviceId: peripheral.id,
856
+ serviceUuid: selectedService.uuid,
857
+ writeUuid: writeCharacteristic.uuid,
858
+ notifyUuid: notifyCharacteristic.uuid,
859
+ });
738
860
  return { write: writeCharacteristic, notify: notifyCharacteristic };
739
861
  }))();
740
862
  try {
@@ -755,27 +877,17 @@ function forceReconnectPeripheral(peripheral, deviceId) {
755
877
  reason: 'force-reconnect',
756
878
  });
757
879
  if (peripheral.state === 'connected') {
758
- peripheral.removeAllListeners('disconnect');
759
- yield new Promise(resolve => {
760
- peripheral.disconnect(() => {
761
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
762
- resolve();
763
- });
764
- });
880
+ yield runBleCallbackOperation(callback => peripheral.disconnect(() => {
881
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
882
+ callback();
883
+ }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
765
884
  }
766
- yield new Promise((resolve, reject) => {
767
- peripheral.connect((error) => {
768
- if (error) {
769
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Force reconnect failed:', error);
770
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
771
- }
772
- else {
773
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
774
- connectedDevices.set(deviceId, peripheral);
775
- resolve();
776
- }
777
- });
885
+ yield runBleCallbackOperation(callback => peripheral.connect(callback), {
886
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
887
+ timeoutBehavior: 'reject',
778
888
  });
889
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
890
+ connectedDevices.set(deviceId, peripheral);
779
891
  yield hdShared.wait(500);
780
892
  });
781
893
  }
@@ -852,18 +964,83 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
852
964
  }
853
965
  function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
854
966
  return index.__awaiter(this, void 0, void 0, function* () {
967
+ setupDisconnectListener(peripheral, deviceId, webContents);
968
+ try {
969
+ return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
970
+ }
971
+ catch (initialError) {
972
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
973
+ deviceId,
974
+ error: initialError,
975
+ });
976
+ }
855
977
  yield forceReconnectPeripheral(peripheral, deviceId);
856
978
  setupDisconnectListener(peripheral, deviceId, webContents);
857
979
  try {
858
980
  return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
859
981
  }
860
- catch (error) {
861
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...', error);
982
+ catch (reconnectError) {
983
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...', reconnectError);
862
984
  return freshScanAndDiscover(deviceId, webContents);
863
985
  }
864
986
  });
865
987
  }
988
+ const DIRECT_CONNECT_TIMEOUT_MS = 2000;
989
+ const DIRECT_CONNECT_COOLDOWN_MS = 15000;
990
+ const directConnectCooldownUntil = new Map();
991
+ function tryDirectConnectById(deviceId) {
992
+ var _a;
993
+ return index.__awaiter(this, void 0, void 0, function* () {
994
+ const nobleInstance = noble;
995
+ if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
996
+ return undefined;
997
+ const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
998
+ if (Date.now() < cooldownUntil)
999
+ return undefined;
1000
+ try {
1001
+ const directPromise = nobleInstance.connectAsync(deviceId);
1002
+ const raced = yield Promise.race([
1003
+ directPromise,
1004
+ new Promise(resolve => {
1005
+ setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
1006
+ }),
1007
+ ]);
1008
+ if (raced === 'timeout') {
1009
+ directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
1010
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
1011
+ deviceId,
1012
+ });
1013
+ directPromise
1014
+ .then(late => {
1015
+ const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
1016
+ if (latePeripheral &&
1017
+ latePeripheral.state === 'connected' &&
1018
+ !connectedDevices.has(deviceId)) {
1019
+ latePeripheral.removeAllListeners('disconnect');
1020
+ latePeripheral.disconnect(() => undefined);
1021
+ }
1022
+ })
1023
+ .catch(() => undefined);
1024
+ return undefined;
1025
+ }
1026
+ const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
1027
+ if (!peripheral || peripheral.state !== 'connected')
1028
+ return undefined;
1029
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id succeeded', { deviceId });
1030
+ return peripheral;
1031
+ }
1032
+ catch (error) {
1033
+ directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
1034
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id failed, falling back to scan', {
1035
+ deviceId,
1036
+ error: String(error),
1037
+ });
1038
+ return undefined;
1039
+ }
1040
+ });
1041
+ }
866
1042
  function connectDevice(deviceId, webContents) {
1043
+ var _a;
867
1044
  return index.__awaiter(this, void 0, void 0, function* () {
868
1045
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
869
1046
  deviceId,
@@ -873,7 +1050,7 @@ function connectDevice(deviceId, webContents) {
873
1050
  totalDiscovered: discoveredDevices.size,
874
1051
  totalConnected: connectedDevices.size,
875
1052
  });
876
- let peripheral = discoveredDevices.get(deviceId);
1053
+ let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
877
1054
  if (!peripheral) {
878
1055
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
879
1056
  if (!noble) {
@@ -882,6 +1059,12 @@ function connectDevice(deviceId, webContents) {
882
1059
  if (!noble) {
883
1060
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
884
1061
  }
1062
+ peripheral = yield tryDirectConnectById(deviceId);
1063
+ if (peripheral) {
1064
+ discoveredDevices.set(deviceId, peripheral);
1065
+ }
1066
+ }
1067
+ if (!peripheral) {
885
1068
  try {
886
1069
  const foundPeripheral = yield performTargetedScan(deviceId);
887
1070
  if (!foundPeripheral) {
@@ -902,8 +1085,8 @@ function connectDevice(deviceId, webContents) {
902
1085
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
903
1086
  if (!connectedDevices.has(deviceId)) {
904
1087
  connectedDevices.set(deviceId, peripheral);
905
- setupDisconnectListener(peripheral, deviceId, webContents);
906
1088
  }
1089
+ setupDisconnectListener(peripheral, deviceId, webContents);
907
1090
  if (deviceCharacteristics.has(deviceId)) {
908
1091
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
909
1092
  const ongoingOperation = subscriptionOperations.get(deviceId);
@@ -925,18 +1108,37 @@ function connectDevice(deviceId, webContents) {
925
1108
  notificationCallbacks.delete(deviceId);
926
1109
  subscribedDevices.delete(deviceId);
927
1110
  }
928
- const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
929
- deviceCharacteristics.set(deviceId, characteristics);
930
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1111
+ try {
1112
+ const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
1113
+ deviceCharacteristics.set(deviceId, characteristics);
1114
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
1115
+ }
1116
+ catch (setupError) {
1117
+ logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed on kept-alive link:', setupError);
1118
+ yield disconnectDevice(deviceId).catch(() => undefined);
1119
+ throw setupError;
1120
+ }
931
1121
  return;
932
1122
  }
933
1123
  return new Promise((resolve, reject) => {
1124
+ let connectionTimedOut = false;
934
1125
  const timeout = setTimeout(() => {
1126
+ connectionTimedOut = true;
935
1127
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
936
- }, CONNECTION_TIMEOUT);
1128
+ }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
937
1129
  const connectedPeripheral = peripheral;
938
1130
  connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
939
1131
  clearTimeout(timeout);
1132
+ if (connectionTimedOut) {
1133
+ if (!error) {
1134
+ try {
1135
+ connectedPeripheral.disconnect(() => undefined);
1136
+ }
1137
+ catch (_a) {
1138
+ }
1139
+ }
1140
+ return;
1141
+ }
940
1142
  if (error) {
941
1143
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
942
1144
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
@@ -966,17 +1168,20 @@ function disconnectDevice(deviceId) {
966
1168
  if (!peripheral) {
967
1169
  return;
968
1170
  }
969
- return new Promise(resolve => {
970
- peripheral.removeAllListeners('disconnect');
971
- peripheral.disconnect(() => {
972
- cleanupDevice(deviceId, undefined, {
973
- cleanupConnection: true,
974
- sendDisconnectEvent: false,
975
- cancelOperations: true,
976
- reason: 'manual-disconnect',
977
- });
978
- resolve();
979
- });
1171
+ const disconnectEntry = deviceDisconnectListeners.get(deviceId);
1172
+ if (disconnectEntry) {
1173
+ disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
1174
+ deviceDisconnectListeners.delete(deviceId);
1175
+ }
1176
+ yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
1177
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
1178
+ timeoutBehavior: 'resolve',
1179
+ });
1180
+ cleanupDevice(deviceId, undefined, {
1181
+ cleanupConnection: true,
1182
+ sendDisconnectEvent: false,
1183
+ cancelOperations: true,
1184
+ reason: 'manual-disconnect',
980
1185
  });
981
1186
  });
982
1187
  }
@@ -991,18 +1196,11 @@ function unsubscribeNotifications(deviceId) {
991
1196
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Unsubscribing from notifications for device:', deviceId);
992
1197
  subscriptionOperations.set(deviceId, 'unsubscribing');
993
1198
  try {
994
- yield new Promise((resolve, reject) => {
995
- notifyCharacteristic.unsubscribe((error) => {
996
- if (error) {
997
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Notification unsubscription failed:', error);
998
- reject(error);
999
- }
1000
- else {
1001
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription successful');
1002
- resolve();
1003
- }
1004
- });
1199
+ yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
1200
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
1201
+ timeoutBehavior: 'resolve',
1005
1202
  });
1203
+ logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription completed');
1006
1204
  notifyCharacteristic.removeAllListeners('data');
1007
1205
  notificationCallbacks.delete(deviceId);
1008
1206
  subscribedDevices.delete(deviceId);
@@ -1054,19 +1252,13 @@ function subscribeNotifications(deviceId, callback) {
1054
1252
  notificationCallbacks.set(deviceId, callback);
1055
1253
  function rebuildAppSubscription(deviceId, notifyCharacteristic) {
1056
1254
  return index.__awaiter(this, void 0, void 0, function* () {
1057
- yield new Promise(resolve => {
1058
- notifyCharacteristic.unsubscribe(() => {
1059
- resolve();
1060
- });
1255
+ yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
1256
+ timeoutMs: BLE_CLEANUP_TIMEOUT,
1257
+ timeoutBehavior: 'resolve',
1061
1258
  });
1062
- yield new Promise((resolve, reject) => {
1063
- notifyCharacteristic.subscribe((error) => {
1064
- if (error) {
1065
- reject(error);
1066
- return;
1067
- }
1068
- resolve();
1069
- });
1259
+ yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1260
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1261
+ timeoutBehavior: 'reject',
1070
1262
  });
1071
1263
  notifyCharacteristic.on('data', (data) => {
1072
1264
  if (!pairedDevices.has(deviceId)) {
@@ -1087,19 +1279,21 @@ function subscribeNotifications(deviceId, callback) {
1087
1279
  });
1088
1280
  }
1089
1281
  function setupNobleBleHandlers(webContents) {
1090
- console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
1091
1282
  try {
1092
- console.log('[NobleBLE] NOBLE_VERSION_771');
1093
1283
  logger = require('electron-log');
1094
- console.log('[NobleBLE] electron-log loaded successfully.');
1095
1284
  const { ipcMain } = require('electron');
1096
- console.log('[NobleBLE] electron.ipcMain loaded successfully.');
1285
+ const handle = (channel, listener) => {
1286
+ ipcMain.removeHandler(channel);
1287
+ ipcMain.handle(channel, listener);
1288
+ };
1097
1289
  safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
1098
- console.log(`[NobleBLE] Registering handler for: ${hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
1099
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
1290
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
1100
1291
  try {
1101
1292
  const devices = yield enumerateDevices();
1102
- safeLog(logger, 'info', 'Enumeration completed, devices:', devices);
1293
+ safeLog(logger, 'debug', 'Enumeration completed', {
1294
+ count: devices.length,
1295
+ devices: devices.map(device => ({ id: device.id, name: device.name })),
1296
+ });
1103
1297
  return devices;
1104
1298
  }
1105
1299
  catch (error) {
@@ -1107,35 +1301,54 @@ function setupNobleBleHandlers(webContents) {
1107
1301
  throw error;
1108
1302
  }
1109
1303
  }));
1110
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_STOP_SCAN, () => index.__awaiter(this, void 0, void 0, function* () {
1304
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_STOP_SCAN, () => index.__awaiter(this, void 0, void 0, function* () {
1111
1305
  yield stopScanning();
1112
1306
  }));
1113
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_GET_DEVICE, (_event, deviceId) => getDevice(deviceId));
1114
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1307
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_GET_DEVICE, (_event, deviceId) => getDevice(deviceId));
1308
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1115
1309
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC CONNECT request received:', {
1116
1310
  deviceId,
1117
1311
  hasPeripheral: connectedDevices.has(deviceId),
1118
1312
  hasCharacteristics: deviceCharacteristics.has(deviceId),
1119
1313
  totalConnectedDevices: connectedDevices.size,
1120
1314
  });
1121
- yield connectDevice(deviceId, webContents);
1315
+ clearIdleDisconnect(deviceId);
1316
+ yield awaitIdleDisconnect(deviceId);
1317
+ try {
1318
+ yield connectDevice(deviceId, webContents);
1319
+ }
1320
+ finally {
1321
+ if (connectedDevices.has(deviceId)) {
1322
+ armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1323
+ }
1324
+ }
1122
1325
  }));
1123
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1326
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE, (_event, deviceId, keepSession) => {
1327
+ if (!connectedDevices.has(deviceId))
1328
+ return;
1329
+ if (keepSession) {
1330
+ armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1331
+ return;
1332
+ }
1333
+ armIdleDisconnect(deviceId);
1334
+ });
1335
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1124
1336
  yield disconnectDevice(deviceId);
1125
1337
  }));
1126
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
1127
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
1128
- yield transmitHexDataToDevice(deviceId, hexData);
1338
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData, options) => index.__awaiter(this, void 0, void 0, function* () {
1339
+ yield transmitHexDataToDevice(deviceId, hexData, options);
1129
1340
  }));
1130
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1341
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1131
1342
  yield subscribeNotifications(deviceId, (data) => {
1132
1343
  webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
1133
1344
  });
1345
+ armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1134
1346
  }));
1135
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1347
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1136
1348
  yield unsubscribeNotifications(deviceId);
1349
+ armIdleDisconnect(deviceId);
1137
1350
  }));
1138
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
1351
+ handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
1139
1352
  const deviceIds = Array.from(connectedDevices.keys());
1140
1353
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Cancel pairing invoked', {
1141
1354
  platform: process.platform,
@@ -1151,7 +1364,7 @@ function setupNobleBleHandlers(webContents) {
1151
1364
  }
1152
1365
  }
1153
1366
  }));
1154
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK, () => index.__awaiter(this, void 0, void 0, function* () {
1367
+ handle(hdShared.EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK, () => index.__awaiter(this, void 0, void 0, function* () {
1155
1368
  try {
1156
1369
  const bluetoothStatus = yield checkBluetoothAvailability();
1157
1370
  safeLog(logger, 'info', 'Bluetooth availability check completed:', bluetoothStatus);
@@ -1169,23 +1382,23 @@ function setupNobleBleHandlers(webContents) {
1169
1382
  }));
1170
1383
  webContents.on('destroyed', () => {
1171
1384
  safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1172
- const deviceIds = Array.from(connectedDevices.keys());
1173
- deviceIds.forEach(deviceId => {
1174
- cleanupDevice(deviceId, undefined, {
1175
- cleanupConnection: true,
1176
- sendDisconnectEvent: false,
1177
- cancelOperations: true,
1178
- reason: 'app-quit',
1179
- });
1385
+ (() => index.__awaiter(this, void 0, void 0, function* () {
1386
+ const deviceIds = Array.from(connectedDevices.keys());
1387
+ for (const deviceId of deviceIds) {
1388
+ yield unsubscribeNotifications(deviceId).catch(() => undefined);
1389
+ yield disconnectDevice(deviceId).catch(() => undefined);
1390
+ }
1391
+ yield stopScanning().catch(() => undefined);
1392
+ if (noble && persistentStateListener) {
1393
+ noble.removeListener('stateChange', persistentStateListener);
1394
+ persistentStateListener = null;
1395
+ }
1396
+ cleanupNobleListeners();
1397
+ discoveredDevices.clear();
1398
+ safeLog(logger, 'info', 'Noble BLE cleanup completed');
1399
+ }))().catch(error => {
1400
+ safeLog(logger, 'error', 'Noble BLE cleanup failed', error);
1180
1401
  });
1181
- stopScanning();
1182
- if (noble && persistentStateListener) {
1183
- noble.removeListener('stateChange', persistentStateListener);
1184
- persistentStateListener = null;
1185
- }
1186
- cleanupNobleListeners();
1187
- discoveredDevices.clear();
1188
- safeLog(logger, 'info', 'Noble BLE cleanup completed');
1189
1402
  });
1190
1403
  safeLog(logger, 'info', 'Noble BLE IPC handlers setup completed');
1191
1404
  }
@@ -1195,4 +1408,5 @@ function setupNobleBleHandlers(webContents) {
1195
1408
  }
1196
1409
  }
1197
1410
 
1411
+ exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
1198
1412
  exports.setupNobleBleHandlers = setupNobleBleHandlers;