@onekeyfe/hd-transport-electron 1.1.34-alpha.2 → 1.1.34-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
32
32
 
33
33
  function initNobleBleSupport(webContents) {
34
34
  return __awaiter(this, void 0, void 0, function* () {
35
- const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-4423b8bd.js'); });
35
+ const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-2e41f27c.js'); });
36
36
  setupNobleBleHandlers(webContents);
37
37
  });
38
38
  }
package/dist/index.d.ts CHANGED
@@ -17,7 +17,6 @@ interface NobleModule {
17
17
  stopScanning(callback?: () => void): void;
18
18
  on(event: 'stateChange', listener: (state: string) => void): void;
19
19
  on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
20
- connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
21
20
  removeListener(event: 'stateChange', listener: (state: string) => void): void;
22
21
  removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
23
22
  }
@@ -37,7 +36,6 @@ interface NobleBleAPI {
37
36
  name: string;
38
37
  } | null>;
39
38
  connect: (uuid: string) => Promise<void>;
40
- release?: (uuid: string) => Promise<void>;
41
39
  disconnect: (uuid: string) => Promise<void>;
42
40
  subscribe: (uuid: string) => Promise<void>;
43
41
  unsubscribe: (uuid: string) => Promise<void>;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var index = require('./index-b69f3b16.js');
5
+ var index = require('./index-de36348d.js');
6
6
 
7
7
 
8
8
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-b69f3b16.js');
3
+ var index = require('./index-de36348d.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var hdTransport = require('@onekeyfe/hd-transport');
6
6
  var pRetry = require('p-retry');
@@ -78,10 +78,6 @@ const subscribedDevices = new Map();
78
78
  const subscriptionOperations = new Map();
79
79
  const devicePacketStates = new Map();
80
80
  const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
81
- const uuid16Key = (uuid) => {
82
- const stripped = (uuid !== null && uuid !== void 0 ? uuid : '').replace(/-/g, '').toLowerCase();
83
- return stripped.length >= 8 ? stripped.substring(4, 8) : stripped;
84
- };
85
81
  const NORMALIZED_WRITE_UUID = '0002';
86
82
  const NORMALIZED_NOTIFY_UUID = '0003';
87
83
  const BLUETOOTH_INIT_TIMEOUT = 10000;
@@ -89,9 +85,6 @@ const DEVICE_SCAN_TIMEOUT = 5000;
89
85
  const FAST_SCAN_TIMEOUT = 1500;
90
86
  const DEVICE_CHECK_INTERVAL = 500;
91
87
  const CONNECTION_TIMEOUT = 3000;
92
- const BLE_IDLE_DISCONNECT_MS = 60000;
93
- const BLE_DISCONNECT_TIMEOUT_MS = 2000;
94
- const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
95
88
  const SERVICE_DISCOVERY_TIMEOUT = 10000;
96
89
  const BLE_PACKET_SIZE = 192;
97
90
  const UNIFIED_WRITE_DELAY = 5;
@@ -251,48 +244,6 @@ function updateBluetoothState(state) {
251
244
  bluetoothState.initialized = true;
252
245
  }
253
246
  }
254
- const idleDisconnectTimers = new Map();
255
- function clearIdleDisconnect(deviceId) {
256
- const timer = idleDisconnectTimers.get(deviceId);
257
- if (timer) {
258
- clearTimeout(timer);
259
- idleDisconnectTimers.delete(deviceId);
260
- }
261
- }
262
- function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
263
- clearIdleDisconnect(deviceId);
264
- idleDisconnectTimers.set(deviceId, setTimeout(() => {
265
- var _a;
266
- idleDisconnectTimers.delete(deviceId);
267
- if (!connectedDevices.has(deviceId))
268
- return;
269
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device:', deviceId, reason);
270
- const peripheral = connectedDevices.get(deviceId);
271
- const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
272
- disconnectDevice(deviceId)
273
- .then(() => {
274
- broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
275
- id: deviceId,
276
- name: deviceName,
277
- });
278
- })
279
- .catch(error => {
280
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Keep-alive disconnect failed:', error);
281
- });
282
- }, ms));
283
- }
284
- function broadcastToAllWebContents(channel, payload) {
285
- try {
286
- const { webContents: electronWebContents } = require('electron');
287
- for (const wc of electronWebContents.getAllWebContents()) {
288
- if (!wc.isDestroyed())
289
- wc.send(channel, payload);
290
- }
291
- }
292
- catch (error) {
293
- logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
294
- }
295
- }
296
247
  function initializeNoble() {
297
248
  return index.__awaiter(this, void 0, void 0, function* () {
298
249
  if (noble)
@@ -364,7 +315,6 @@ function cleanupDevice(deviceId, webContents, options = {}) {
364
315
  sendDisconnectEvent,
365
316
  cancelOperations,
366
317
  });
367
- clearIdleDisconnect(deviceId);
368
318
  const peripheral = connectedDevices.get(deviceId);
369
319
  const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
370
320
  if (cleanupConnection) {
@@ -507,7 +457,6 @@ function transmitHexDataToDevice(deviceId, hexData) {
507
457
  if (!peripheral || !characteristics) {
508
458
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
509
459
  }
510
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
511
460
  const toBuffer = Buffer.from(hexData, 'hex');
512
461
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Writing data:', {
513
462
  deviceId,
@@ -757,7 +706,7 @@ function discoverServicesAndCharacteristics(peripheral) {
757
706
  });
758
707
  const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
759
708
  const services = yield new Promise((resolve, reject) => {
760
- peripheral.discoverServices([], (error, svc) => {
709
+ peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
761
710
  if (error) {
762
711
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
763
712
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
@@ -770,14 +719,10 @@ function discoverServicesAndCharacteristics(peripheral) {
770
719
  if (!services || services.length === 0) {
771
720
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
772
721
  }
773
- const wanted = ONEKEY_SERVICE_UUIDS.map(uuid16Key);
774
- const service = services.find(svc => wanted.includes(uuid16Key(svc.uuid)));
775
- if (!service) {
776
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, `No OneKey service in result set: ${services.map(svc => svc.uuid).join('|')}`);
777
- }
722
+ const service = services[0];
778
723
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found service:', service.uuid);
779
724
  const characteristics = yield new Promise((resolve, reject) => {
780
- service.discoverCharacteristics([], (error, chars) => {
725
+ service.discoverCharacteristics([hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID, hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID], (error, chars) => {
781
726
  if (error) {
782
727
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
783
728
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
@@ -856,21 +801,8 @@ function forceReconnectPeripheral(peripheral, deviceId) {
856
801
  });
857
802
  }
858
803
  function freshScanAndDiscover(deviceId, webContents) {
859
- var _a;
860
804
  return index.__awaiter(this, void 0, void 0, function* () {
861
805
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Performing fresh scan to get new peripheral object for device:', deviceId);
862
- const stalePeripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : discoveredDevices.get(deviceId);
863
- if (stalePeripheral && stalePeripheral.state === 'connected') {
864
- stalePeripheral.removeAllListeners('disconnect');
865
- yield new Promise(resolve => {
866
- const timer = setTimeout(resolve, BLE_DISCONNECT_TIMEOUT_MS);
867
- stalePeripheral.disconnect(() => {
868
- clearTimeout(timer);
869
- resolve();
870
- });
871
- });
872
- connectedDevices.delete(deviceId);
873
- }
874
806
  const freshPeripheral = yield performTargetedScan(deviceId);
875
807
  if (!freshPeripheral) {
876
808
  discoveredDevices.delete(deviceId);
@@ -895,13 +827,7 @@ function freshScanAndDiscover(deviceId, webContents) {
895
827
  setupDisconnectListener(freshPeripheral, deviceId, webContents);
896
828
  yield hdShared.wait(500);
897
829
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
898
- try {
899
- return yield discoverServicesAndCharacteristics(freshPeripheral);
900
- }
901
- catch (error) {
902
- yield disconnectDevice(deviceId).catch(() => undefined);
903
- throw error;
904
- }
830
+ return discoverServicesAndCharacteristics(freshPeripheral);
905
831
  });
906
832
  }
907
833
  function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
@@ -947,22 +873,10 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
947
873
  }
948
874
  function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
949
875
  return index.__awaiter(this, void 0, void 0, function* () {
876
+ yield forceReconnectPeripheral(peripheral, deviceId);
950
877
  setupDisconnectListener(peripheral, deviceId, webContents);
951
- if (peripheral.state === 'connected') {
952
- try {
953
- const result = yield discoverServicesAndCharacteristics(peripheral);
954
- connectedDevices.set(deviceId, peripheral);
955
- return result;
956
- }
957
- catch (directError) {
958
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct discovery miss, escalating:', String(directError));
959
- }
960
- }
961
878
  try {
962
- yield forceReconnectPeripheral(peripheral, deviceId);
963
- setupDisconnectListener(peripheral, deviceId, webContents);
964
- const result = yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
965
- return result;
879
+ return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
966
880
  }
967
881
  catch (error) {
968
882
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...', error);
@@ -970,51 +884,6 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
970
884
  }
971
885
  });
972
886
  }
973
- const DIRECT_CONNECT_TIMEOUT_MS = 2000;
974
- const DIRECT_CONNECT_COOLDOWN_MS = 15000;
975
- const directConnectCooldownUntil = new Map();
976
- function tryDirectConnectById(deviceId) {
977
- var _a;
978
- return index.__awaiter(this, void 0, void 0, function* () {
979
- if (!noble || typeof noble.connectAsync !== 'function')
980
- return undefined;
981
- if (((_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0) > Date.now()) {
982
- return undefined;
983
- }
984
- try {
985
- const directPromise = noble.connectAsync(deviceId);
986
- const raced = yield Promise.race([
987
- directPromise,
988
- new Promise(resolve => {
989
- setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
990
- }),
991
- ]);
992
- if (raced === 'timeout') {
993
- directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
994
- directPromise
995
- .then(late => {
996
- const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
997
- if (latePeripheral &&
998
- latePeripheral.state === 'connected' &&
999
- !connectedDevices.has(deviceId)) {
1000
- latePeripheral.removeAllListeners('disconnect');
1001
- latePeripheral.disconnect(() => { });
1002
- }
1003
- })
1004
- .catch(() => { });
1005
- return undefined;
1006
- }
1007
- const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
1008
- if (!peripheral || peripheral.state !== 'connected')
1009
- return undefined;
1010
- discoveredDevices.set(deviceId, peripheral);
1011
- return peripheral;
1012
- }
1013
- catch (error) {
1014
- return undefined;
1015
- }
1016
- });
1017
- }
1018
887
  function connectDevice(deviceId, webContents) {
1019
888
  return index.__awaiter(this, void 0, void 0, function* () {
1020
889
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
@@ -1034,9 +903,8 @@ function connectDevice(deviceId, webContents) {
1034
903
  if (!noble) {
1035
904
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
1036
905
  }
1037
- peripheral = yield tryDirectConnectById(deviceId);
1038
906
  try {
1039
- const foundPeripheral = peripheral !== null && peripheral !== void 0 ? peripheral : (yield performTargetedScan(deviceId));
907
+ const foundPeripheral = yield performTargetedScan(deviceId);
1040
908
  if (!foundPeripheral) {
1041
909
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found even after targeted scan`);
1042
910
  }
@@ -1085,21 +953,12 @@ function connectDevice(deviceId, webContents) {
1085
953
  return;
1086
954
  }
1087
955
  return new Promise((resolve, reject) => {
1088
- let timedOut = false;
1089
956
  const timeout = setTimeout(() => {
1090
- timedOut = true;
1091
957
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1092
958
  }, CONNECTION_TIMEOUT);
1093
959
  const connectedPeripheral = peripheral;
1094
960
  connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
1095
961
  clearTimeout(timeout);
1096
- if (timedOut) {
1097
- if (!error && connectedPeripheral.state === 'connected') {
1098
- logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Late connection after timeout, disconnecting:', deviceId);
1099
- connectedPeripheral.disconnect(() => { });
1100
- }
1101
- return;
1102
- }
1103
962
  if (error) {
1104
963
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
1105
964
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
@@ -1131,11 +990,7 @@ function disconnectDevice(deviceId) {
1131
990
  }
1132
991
  return new Promise(resolve => {
1133
992
  peripheral.removeAllListeners('disconnect');
1134
- let settled = false;
1135
- const finish = () => {
1136
- if (settled)
1137
- return;
1138
- settled = true;
993
+ peripheral.disconnect(() => {
1139
994
  cleanupDevice(deviceId, undefined, {
1140
995
  cleanupConnection: true,
1141
996
  sendDisconnectEvent: false,
@@ -1143,11 +998,6 @@ function disconnectDevice(deviceId) {
1143
998
  reason: 'manual-disconnect',
1144
999
  });
1145
1000
  resolve();
1146
- };
1147
- const timer = setTimeout(finish, BLE_DISCONNECT_TIMEOUT_MS);
1148
- peripheral.disconnect(() => {
1149
- clearTimeout(timer);
1150
- finish();
1151
1001
  });
1152
1002
  });
1153
1003
  });
@@ -1312,17 +1162,11 @@ function setupNobleBleHandlers(webContents) {
1312
1162
  hasCharacteristics: deviceCharacteristics.has(deviceId),
1313
1163
  totalConnectedDevices: connectedDevices.size,
1314
1164
  });
1315
- clearIdleDisconnect(deviceId);
1316
1165
  yield connectDevice(deviceId, webContents);
1317
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1318
1166
  }));
1319
1167
  ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1320
1168
  yield disconnectDevice(deviceId);
1321
1169
  }));
1322
- ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE, (_event, deviceId) => {
1323
- if (connectedDevices.has(deviceId))
1324
- armIdleDisconnect(deviceId);
1325
- });
1326
1170
  ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
1327
1171
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
1328
1172
  yield transmitHexDataToDevice(deviceId, hexData);
@@ -1331,11 +1175,9 @@ function setupNobleBleHandlers(webContents) {
1331
1175
  yield subscribeNotifications(deviceId, (data) => {
1332
1176
  webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
1333
1177
  });
1334
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1335
1178
  }));
1336
1179
  ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
1337
1180
  yield unsubscribeNotifications(deviceId);
1338
- armIdleDisconnect(deviceId);
1339
1181
  }));
1340
1182
  ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
1341
1183
  const deviceIds = Array.from(connectedDevices.keys());
@@ -1373,19 +1215,12 @@ function setupNobleBleHandlers(webContents) {
1373
1215
  safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1374
1216
  const deviceIds = Array.from(connectedDevices.keys());
1375
1217
  deviceIds.forEach(deviceId => {
1376
- const peripheral = connectedDevices.get(deviceId);
1377
1218
  cleanupDevice(deviceId, undefined, {
1378
1219
  cleanupConnection: true,
1379
1220
  sendDisconnectEvent: false,
1380
1221
  cancelOperations: true,
1381
1222
  reason: 'app-quit',
1382
1223
  });
1383
- if (peripheral && peripheral.state === 'connected') {
1384
- peripheral.removeAllListeners('disconnect');
1385
- peripheral.disconnect(() => {
1386
- safeLog(logger, 'info', `Disconnected ${deviceId} on window teardown`);
1387
- });
1388
- }
1389
1224
  });
1390
1225
  stopScanning();
1391
1226
  if (noble && persistentStateListener) {
@@ -1 +1 @@
1
- {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AA8uDhE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAmMpE"}
1
+ {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAshDhE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAsKpE"}
@@ -8,7 +8,6 @@ export interface NobleBleAPI {
8
8
  name: string;
9
9
  } | null>;
10
10
  connect: (uuid: string) => Promise<void>;
11
- release?: (uuid: string) => Promise<void>;
12
11
  disconnect: (uuid: string) => Promise<void>;
13
12
  subscribe: (uuid: string) => Promise<void>;
14
13
  unsubscribe: (uuid: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,oBAAoB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/F,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
1
+ {"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,oBAAoB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/F,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
@@ -15,7 +15,6 @@ export interface NobleModule {
15
15
  stopScanning(callback?: () => void): void;
16
16
  on(event: 'stateChange', listener: (state: string) => void): void;
17
17
  on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
18
- connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
19
18
  removeListener(event: 'stateChange', listener: (state: string) => void): void;
20
19
  removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
21
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IASxE,YAAY,CAAC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IACpE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
1
+ {"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-electron",
3
- "version": "1.1.34-alpha.2",
3
+ "version": "1.1.34-alpha.4",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -25,9 +25,9 @@
25
25
  "electron-log": ">=4.0.0"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-core": "1.1.34-alpha.2",
29
- "@onekeyfe/hd-shared": "1.1.34-alpha.2",
30
- "@onekeyfe/hd-transport": "1.1.34-alpha.2",
28
+ "@onekeyfe/hd-core": "1.1.34-alpha.4",
29
+ "@onekeyfe/hd-shared": "1.1.34-alpha.4",
30
+ "@onekeyfe/hd-transport": "1.1.34-alpha.4",
31
31
  "@stoprocent/noble": "2.3.16",
32
32
  "p-retry": "^4.6.2"
33
33
  },
@@ -36,5 +36,5 @@
36
36
  "electron": "^25.0.0",
37
37
  "typescript": "^5.3.3"
38
38
  },
39
- "gitHead": "dee167c35d191f3f4e02c4cb6228e1f926f929ec"
39
+ "gitHead": "d546bc55410990351d34009230aefea77725d0d6"
40
40
  }
@@ -9,7 +9,9 @@ import {
9
9
  EOneKeyBleMessageKeys,
10
10
  ERRORS,
11
11
  HardwareErrorCode,
12
+ ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
12
13
  ONEKEY_SERVICE_UUID,
14
+ ONEKEY_WRITE_CHARACTERISTIC_UUID,
13
15
  isHeaderChunk,
14
16
  isOnekeyDevice,
15
17
  wait,
@@ -72,13 +74,6 @@ const devicePacketStates = new Map<string, PacketAssemblyState>();
72
74
  const ONEKEY_SERVICE_UUIDS = [ONEKEY_SERVICE_UUID];
73
75
 
74
76
  // Pre-normalized characteristic identifiers for fast comparison
75
- // Reduce any uuid form (long base-form with/without dashes, or short) to its
76
- // 16-bit key for comparisons — noble/mac reports base-UUIDs short-form.
77
- const uuid16Key = (uuid: string): string => {
78
- const stripped = (uuid ?? '').replace(/-/g, '').toLowerCase();
79
- return stripped.length >= 8 ? stripped.substring(4, 8) : stripped;
80
- };
81
-
82
77
  const NORMALIZED_WRITE_UUID = '0002';
83
78
  const NORMALIZED_NOTIFY_UUID = '0003';
84
79
 
@@ -88,15 +83,6 @@ const DEVICE_SCAN_TIMEOUT = 5000; // 5 seconds for device scanning
88
83
  const FAST_SCAN_TIMEOUT = 1500; // 1.5 seconds for fast targeted scanning
89
84
  const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
90
85
  const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
91
- // Idle window before a kept-alive link is dropped so other hosts (phone) can
92
- // connect; matches the device's 60s auto-lock default.
93
- const BLE_IDLE_DISCONNECT_MS = 60_000;
94
- // noble's disconnect callback can hang on a wedged peripheral — bound it.
95
- const BLE_DISCONNECT_TIMEOUT_MS = 2000;
96
- // Backstop while a request is outstanding: the idle clock must not run while
97
- // the user is on the device's confirm screen, but a dead call must not hold
98
- // the link forever either.
99
- const BLE_BUSY_BACKSTOP_MS = 10 * 60_000;
100
86
  const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
101
87
 
102
88
  // Write-related constants
@@ -316,64 +302,6 @@ function updateBluetoothState(state: string): void {
316
302
  }
317
303
  }
318
304
 
319
- // ===== Keep-alive idle disconnect =====
320
- // One timer per device: a write swaps it for the busy backstop, a complete
321
- // response re-arms the 60s idle clock. Lives in the main process so a renderer
322
- // reload cannot orphan a held link.
323
- const idleDisconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();
324
-
325
- function clearIdleDisconnect(deviceId: string): void {
326
- const timer = idleDisconnectTimers.get(deviceId);
327
- if (timer) {
328
- clearTimeout(timer);
329
- idleDisconnectTimers.delete(deviceId);
330
- }
331
- }
332
-
333
- function armIdleDisconnect(
334
- deviceId: string,
335
- ms: number = BLE_IDLE_DISCONNECT_MS,
336
- reason: 'idle' | 'busy-backstop' = 'idle'
337
- ): void {
338
- clearIdleDisconnect(deviceId);
339
- idleDisconnectTimers.set(
340
- deviceId,
341
- setTimeout(() => {
342
- idleDisconnectTimers.delete(deviceId);
343
- if (!connectedDevices.has(deviceId)) return;
344
- logger?.info('[NobleBLE] Keep-alive timeout, disconnecting device:', deviceId, reason);
345
- const peripheral = connectedDevices.get(deviceId);
346
- const deviceName = peripheral?.advertisement?.localName || 'Unknown Device';
347
- disconnectDevice(deviceId)
348
- .then(() => {
349
- // Notify renderers: on the busy backstop a call is still in flight
350
- // and must reject instead of hanging on a dead link.
351
- broadcastToAllWebContents(EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
352
- id: deviceId,
353
- name: deviceName,
354
- });
355
- })
356
- .catch(error => {
357
- logger?.error('[NobleBLE] Keep-alive disconnect failed:', error);
358
- });
359
- }, ms)
360
- );
361
- }
362
-
363
- function broadcastToAllWebContents(channel: string, payload: unknown): void {
364
- try {
365
- // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
366
- const { webContents: electronWebContents } = require('electron') as {
367
- webContents: { getAllWebContents(): WebContents[] };
368
- };
369
- for (const wc of electronWebContents.getAllWebContents()) {
370
- if (!wc.isDestroyed()) wc.send(channel, payload);
371
- }
372
- } catch (error) {
373
- logger?.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
374
- }
375
- }
376
-
377
305
  // Initialize Noble
378
306
  async function initializeNoble(): Promise<void> {
379
307
  if (noble) return;
@@ -490,7 +418,6 @@ function cleanupDevice(
490
418
  sendDisconnectEvent,
491
419
  cancelOperations,
492
420
  });
493
- clearIdleDisconnect(deviceId);
494
421
 
495
422
  // Get device info before cleanup
496
423
  const peripheral = connectedDevices.get(deviceId);
@@ -689,9 +616,6 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
689
616
  `Device ${deviceId} not connected or characteristics not available`
690
617
  );
691
618
  }
692
- // Request outstanding: swap the idle clock for the busy backstop; the
693
- // complete-response handler re-arms the 60s idle clock.
694
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
695
619
 
696
620
  const toBuffer = Buffer.from(hexData, 'hex');
697
621
  logger?.info('[NobleBLE] Writing data:', {
@@ -1028,13 +952,9 @@ async function discoverServicesAndCharacteristics(
1028
952
 
1029
953
  // Main discovery logic as async function
1030
954
  const discoveryPromise = (async (): Promise<CharacteristicPair> => {
1031
- // Step 1: Discover services (promisified). In unfiltered mode we discover
1032
- // everything and pick the OneKey service in JS — field data (Classic,
1033
- // 2026-07-19) showed the UUID-filtered query returning empty on a link
1034
- // where an unfiltered probe saw 5 services, so the filter itself is under
1035
- // suspicion (same failure family as the Windows scan-filter issue).
955
+ // Step 1: Discover services (promisified)
1036
956
  const services = await new Promise<Service[]>((resolve, reject) => {
1037
- peripheral.discoverServices([], (error, svc) => {
957
+ peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
1038
958
  if (error) {
1039
959
  logger?.error('[NobleBLE] Service discovery failed:', error);
1040
960
  reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
@@ -1048,28 +968,22 @@ async function discoverServicesAndCharacteristics(
1048
968
  throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
1049
969
  }
1050
970
 
1051
- // Pick by 16-bit key: constants are long base-forms, noble/mac reports
1052
- // base-UUID services short-form ('0001').
1053
- const wanted = ONEKEY_SERVICE_UUIDS.map(uuid16Key);
1054
- const service = services.find(svc => wanted.includes(uuid16Key(svc.uuid)));
1055
- if (!service) {
1056
- // Carry the uuid list so the trace shows what the device exposed.
1057
- throw ERRORS.TypedError(
1058
- HardwareErrorCode.BleServiceNotFound,
1059
- `No OneKey service in result set: ${services.map(svc => svc.uuid).join('|')}`
1060
- );
1061
- }
971
+ const service = services[0];
1062
972
  logger?.info('[NobleBLE] Found service:', service.uuid);
1063
973
 
974
+ // Step 2: Discover characteristics (promisified)
1064
975
  const characteristics = await new Promise<Characteristic[]>((resolve, reject) => {
1065
- service.discoverCharacteristics([], (error, chars) => {
1066
- if (error) {
1067
- logger?.error('[NobleBLE] Characteristic discovery failed:', error);
1068
- reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
1069
- } else {
1070
- resolve(chars);
976
+ service.discoverCharacteristics(
977
+ [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
978
+ (error, chars) => {
979
+ if (error) {
980
+ logger?.error('[NobleBLE] Characteristic discovery failed:', error);
981
+ reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
982
+ } else {
983
+ resolve(chars);
984
+ }
1071
985
  }
1072
- });
986
+ );
1073
987
  });
1074
988
 
1075
989
  // Step 3: Find required characteristics
@@ -1172,6 +1086,7 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
1172
1086
  // NOTE: Caller MUST call setupDisconnectListener() after this function returns
1173
1087
  }
1174
1088
 
1089
+ // Last resort: Fresh scan to get completely new peripheral object and discover services
1175
1090
  async function freshScanAndDiscover(
1176
1091
  deviceId: string,
1177
1092
  webContents: WebContents
@@ -1181,21 +1096,6 @@ async function freshScanAndDiscover(
1181
1096
  deviceId
1182
1097
  );
1183
1098
 
1184
- // The device does not advertise while we hold a link — drop the old one
1185
- // first (bounded) so it goes back on air before we scan.
1186
- const stalePeripheral = connectedDevices.get(deviceId) ?? discoveredDevices.get(deviceId);
1187
- if (stalePeripheral && stalePeripheral.state === 'connected') {
1188
- stalePeripheral.removeAllListeners('disconnect');
1189
- await new Promise<void>(resolve => {
1190
- const timer = setTimeout(resolve, BLE_DISCONNECT_TIMEOUT_MS);
1191
- stalePeripheral.disconnect(() => {
1192
- clearTimeout(timer);
1193
- resolve();
1194
- });
1195
- });
1196
- connectedDevices.delete(deviceId);
1197
- }
1198
-
1199
1099
  const freshPeripheral = await performTargetedScan(deviceId);
1200
1100
  if (!freshPeripheral) {
1201
1101
  // Deep cleanup: fresh scan found no device, reset to initial state
@@ -1236,17 +1136,9 @@ async function freshScanAndDiscover(
1236
1136
  // Wait for connection to stabilize (fresh peripheral doesn't need GATT cache clearing)
1237
1137
  await wait(500);
1238
1138
 
1239
- // Attempt service discovery with fresh peripheral. On failure the fresh
1240
- // peripheral is already physically connected and registered in
1241
- // connectedDevices with no idle timer armed — keep-alive release() is
1242
- // logical-only and would never drop it — so tear it down before rethrowing.
1139
+ // Attempt service discovery with fresh peripheral
1243
1140
  logger?.info('[NobleBLE] Attempting service discovery with fresh peripheral');
1244
- try {
1245
- return await discoverServicesAndCharacteristics(freshPeripheral);
1246
- } catch (error) {
1247
- await disconnectDevice(deviceId).catch(() => undefined);
1248
- throw error;
1249
- }
1141
+ return discoverServicesAndCharacteristics(freshPeripheral);
1250
1142
  }
1251
1143
 
1252
1144
  // Enhanced service discovery with p-retry for robust BLE connection
@@ -1317,94 +1209,20 @@ async function setupConnectionAndDiscoverServices(
1317
1209
  deviceId: string,
1318
1210
  webContents: WebContents
1319
1211
  ): Promise<CharacteristicPair> {
1212
+ // Force reconnect to clear GATT cache
1213
+ await forceReconnectPeripheral(peripheral, deviceId);
1320
1214
  setupDisconnectListener(peripheral, deviceId, webContents);
1321
1215
 
1322
- // Optimistic direct discovery first; the force-reconnect fallback below only
1323
- // runs if it fails. Discovery is UNFILTERED with 16-bit-key matching in JS:
1324
- // the device answers only the 2-byte-encoded targeted query, so a uuid-
1325
- // filtered discovery is unreliable (macOS CoreBluetooth sends the 128-bit
1326
- // base form and misses). See uuid16Key.
1327
- if (peripheral.state === 'connected') {
1328
- try {
1329
- const result = await discoverServicesAndCharacteristics(peripheral);
1330
- connectedDevices.set(deviceId, peripheral);
1331
- return result;
1332
- } catch (directError) {
1333
- logger?.info('[NobleBLE] Direct discovery miss, escalating:', String(directError));
1334
- }
1335
- }
1336
-
1337
- // Fallback ladder; a force-reconnect failure must also escalate to the
1338
- // fresh scan instead of propagating out.
1216
+ // Discover services and characteristics with retry
1339
1217
  try {
1340
- await forceReconnectPeripheral(peripheral, deviceId);
1341
- // forceReconnectPeripheral strips listeners — re-attach.
1342
- setupDisconnectListener(peripheral, deviceId, webContents);
1343
-
1344
- const result = await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
1345
- return result;
1218
+ return await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
1346
1219
  } catch (error) {
1347
1220
  // Last resort: fresh scan to get new peripheral object
1348
1221
  logger?.error('[NobleBLE] Service discovery failed, attempting fresh scan...', error);
1349
- // Broken-OS-bond signature lands here.
1350
1222
  return freshScanAndDiscover(deviceId, webContents);
1351
1223
  }
1352
1224
  }
1353
1225
 
1354
- // noble/mac never resolves connect-by-id for an unretrievable peripheral —
1355
- // time-box it, and after a timeout skip direct for a while so the retry
1356
- // ladder doesn't re-pay the 2s against an absent device.
1357
- const DIRECT_CONNECT_TIMEOUT_MS = 2000;
1358
- const DIRECT_CONNECT_COOLDOWN_MS = 15_000;
1359
- const directConnectCooldownUntil = new Map<string, number>();
1360
-
1361
- /**
1362
- * Bounded connect-by-id with no scan; undefined -> caller falls back to the
1363
- * targeted scan. A late success after the timeout is dropped unless claimed.
1364
- */
1365
- async function tryDirectConnectById(deviceId: string): Promise<Peripheral | undefined> {
1366
- if (!noble || typeof noble.connectAsync !== 'function') return undefined;
1367
- if ((directConnectCooldownUntil.get(deviceId) ?? 0) > Date.now()) {
1368
- return undefined;
1369
- }
1370
- try {
1371
- // The late-orphan guard must attach to THIS pending connect.
1372
- const directPromise = noble.connectAsync(deviceId);
1373
- const raced = await Promise.race([
1374
- directPromise,
1375
- new Promise<'timeout'>(resolve => {
1376
- setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
1377
- }),
1378
- ]);
1379
- if (raced === 'timeout') {
1380
- directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
1381
- // If the pending connect completes later, drop it unless someone claimed it.
1382
- directPromise
1383
- .then(late => {
1384
- const latePeripheral = late ?? discoveredDevices.get(deviceId);
1385
- if (
1386
- latePeripheral &&
1387
- latePeripheral.state === 'connected' &&
1388
- !connectedDevices.has(deviceId)
1389
- ) {
1390
- latePeripheral.removeAllListeners('disconnect');
1391
- latePeripheral.disconnect(() => {});
1392
- }
1393
- })
1394
- .catch(() => {});
1395
- return undefined;
1396
- }
1397
- // Backends emit a `discover` for the peripheral as a side effect, so the
1398
- // cache may hold it even when connectAsync resolves without a value.
1399
- const peripheral = raced ?? discoveredDevices.get(deviceId);
1400
- if (!peripheral || peripheral.state !== 'connected') return undefined;
1401
- discoveredDevices.set(deviceId, peripheral);
1402
- return peripheral;
1403
- } catch (error) {
1404
- return undefined;
1405
- }
1406
- }
1407
-
1408
1226
  // Connect to device - supports both discovered and direct connection modes
1409
1227
  async function connectDevice(deviceId: string, webContents: WebContents): Promise<void> {
1410
1228
  logger?.info('[NobleBLE] Connect device request:', {
@@ -1431,13 +1249,9 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1431
1249
  throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
1432
1250
  }
1433
1251
 
1434
- // Bounded connect-by-id first: skips the ~650ms targeted scan and reaches
1435
- // a device that is not advertising; falls through to the scan on timeout.
1436
- peripheral = await tryDirectConnectById(deviceId);
1437
-
1438
1252
  // Perform a targeted scan to find the specific device
1439
1253
  try {
1440
- const foundPeripheral = peripheral ?? (await performTargetedScan(deviceId));
1254
+ const foundPeripheral = await performTargetedScan(deviceId);
1441
1255
  if (!foundPeripheral) {
1442
1256
  throw ERRORS.TypedError(
1443
1257
  HardwareErrorCode.DeviceNotFound,
@@ -1523,11 +1337,7 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1523
1337
  }
1524
1338
 
1525
1339
  return new Promise((resolve, reject) => {
1526
- // After the timeout rejects, a late successful connect would be an
1527
- // ownerless link with no idle timer — detect and drop it.
1528
- let timedOut = false;
1529
1340
  const timeout = setTimeout(() => {
1530
- timedOut = true;
1531
1341
  reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1532
1342
  }, CONNECTION_TIMEOUT);
1533
1343
 
@@ -1536,14 +1346,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1536
1346
  connectedPeripheral.connect(async (error: Error | undefined) => {
1537
1347
  clearTimeout(timeout);
1538
1348
 
1539
- if (timedOut) {
1540
- if (!error && connectedPeripheral.state === 'connected') {
1541
- logger?.info('[NobleBLE] Late connection after timeout, disconnecting:', deviceId);
1542
- connectedPeripheral.disconnect(() => {});
1543
- }
1544
- return;
1545
- }
1546
-
1547
1349
  if (error) {
1548
1350
  logger?.error('[NobleBLE] Connection failed:', error);
1549
1351
  reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
@@ -1584,13 +1386,7 @@ async function disconnectDevice(deviceId: string): Promise<void> {
1584
1386
  // Remove disconnect listener to avoid triggering handleDeviceDisconnect
1585
1387
  peripheral.removeAllListeners('disconnect');
1586
1388
 
1587
- // Time-box the disconnect: on a wedged peripheral CoreBluetooth may never
1588
- // invoke the callback, and callers (e.g. the idle/backstop timer) rely on
1589
- // this resolving so cleanup + the renderer-facing disconnect broadcast run.
1590
- let settled = false;
1591
- const finish = () => {
1592
- if (settled) return;
1593
- settled = true;
1389
+ peripheral.disconnect(() => {
1594
1390
  // Clean up device state using unified function
1595
1391
  cleanupDevice(deviceId, undefined, {
1596
1392
  cleanupConnection: true,
@@ -1599,11 +1395,6 @@ async function disconnectDevice(deviceId: string): Promise<void> {
1599
1395
  reason: 'manual-disconnect',
1600
1396
  });
1601
1397
  resolve();
1602
- };
1603
- const timer = setTimeout(finish, BLE_DISCONNECT_TIMEOUT_MS);
1604
- peripheral.disconnect(() => {
1605
- clearTimeout(timer);
1606
- finish();
1607
1398
  });
1608
1399
  });
1609
1400
  }
@@ -1773,11 +1564,6 @@ async function subscribeNotifications(
1773
1564
  return;
1774
1565
  }
1775
1566
  if (result.isComplete && result.completePacket) {
1776
- // A complete response does NOT mean the operation is over — the run
1777
- // may continue (further request/response, or an on-device prompt whose
1778
- // Ack the host writes after slow human input). Do NOT re-arm the 60s
1779
- // idle clock here; that starts only on logical release. The busy
1780
- // backstop remains the in-flight ceiling.
1781
1567
  const appCb = notificationCallbacks.get(deviceId);
1782
1568
  if (appCb) appCb(result.completePacket);
1783
1569
  }
@@ -1846,15 +1632,7 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1846
1632
  hasCharacteristics: deviceCharacteristics.has(deviceId),
1847
1633
  totalConnectedDevices: connectedDevices.size,
1848
1634
  });
1849
- // An armed idle timer must not fire mid-connect (pairing can take ~30s).
1850
- clearIdleDisconnect(deviceId);
1851
1635
  await connectDevice(deviceId, webContents);
1852
- // A logical operation is now in flight (acquire). Arm the long busy
1853
- // backstop, NOT the 60s idle clock — the 60s countdown starts only when
1854
- // the renderer signals logical release (NOBLE_BLE_RELEASE). This keeps
1855
- // the link up across slow on-device prompts (PIN/passphrase) that have
1856
- // no outstanding write.
1857
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1858
1636
  }
1859
1637
  );
1860
1638
 
@@ -1866,16 +1644,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1866
1644
  }
1867
1645
  );
1868
1646
 
1869
- // Handle logical release: the operation is done, start the 60s idle
1870
- // countdown so an unused device is freed for other hosts. The physical
1871
- // link is kept (reused by the next call) unless the countdown elapses.
1872
- ipcMain.handle(
1873
- EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE,
1874
- (_event: IpcMainInvokeEvent, deviceId: string) => {
1875
- if (connectedDevices.has(deviceId)) armIdleDisconnect(deviceId);
1876
- }
1877
- );
1878
-
1879
1647
  // Handle write request
1880
1648
  ipcMain.handle(
1881
1649
  EOneKeyBleMessageKeys.NOBLE_BLE_WRITE,
@@ -1893,8 +1661,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1893
1661
  // Send data back to renderer process
1894
1662
  webContents.send(EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
1895
1663
  });
1896
- // Still acquiring (in flight) — busy backstop, not the 60s idle clock.
1897
- armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
1898
1664
  }
1899
1665
  );
1900
1666
 
@@ -1903,7 +1669,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1903
1669
  EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE,
1904
1670
  async (_event: IpcMainInvokeEvent, deviceId: string) => {
1905
1671
  await unsubscribeNotifications(deviceId);
1906
- armIdleDisconnect(deviceId);
1907
1672
  }
1908
1673
  );
1909
1674
 
@@ -1949,23 +1714,15 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1949
1714
  webContents.on('destroyed', () => {
1950
1715
  safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
1951
1716
 
1952
- // Physically disconnect too: the main process can outlive this window
1953
- // (tray) and a held link would block the device for every other host.
1717
+ // 1. Clean up all connected devices (unified cleanup, avoid duplicates)
1954
1718
  const deviceIds = Array.from(connectedDevices.keys());
1955
1719
  deviceIds.forEach(deviceId => {
1956
- const peripheral = connectedDevices.get(deviceId);
1957
1720
  cleanupDevice(deviceId, undefined, {
1958
1721
  cleanupConnection: true,
1959
1722
  sendDisconnectEvent: false,
1960
1723
  cancelOperations: true,
1961
1724
  reason: 'app-quit',
1962
1725
  });
1963
- if (peripheral && peripheral.state === 'connected') {
1964
- peripheral.removeAllListeners('disconnect');
1965
- peripheral.disconnect(() => {
1966
- safeLog(logger, 'info', `Disconnected ${deviceId} on window teardown`);
1967
- });
1968
- }
1969
1726
  });
1970
1727
 
1971
1728
  // 2. Stop scanning
@@ -8,8 +8,6 @@ export interface NobleBleAPI {
8
8
  enumerate: () => Promise<{ id: string; name: string }[]>;
9
9
  getDevice: (uuid: string) => Promise<{ id: string; name: string } | null>;
10
10
  connect: (uuid: string) => Promise<void>;
11
- /** Logical end-of-operation (keep-alive): starts the idle-disconnect countdown. */
12
- release?: (uuid: string) => Promise<void>;
13
11
  disconnect: (uuid: string) => Promise<void>;
14
12
  subscribe: (uuid: string) => Promise<void>;
15
13
  unsubscribe: (uuid: string) => Promise<void>;
@@ -30,15 +30,6 @@ export interface NobleModule {
30
30
  stopScanning(callback?: () => void): void;
31
31
  on(event: 'stateChange', listener: (state: string) => void): void;
32
32
  on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
33
- /**
34
- * Connect by id/address with NO scan. Both native backends support this and
35
- * emit a `discover` for the peripheral as a side effect (Windows synthesizes
36
- * one for an unknown address; macOS resolves via
37
- * retrievePeripheralsWithIdentifiers). Optional so an old noble can omit it.
38
- * CAUTION: on macOS this never resolves for an id CoreBluetooth cannot
39
- * retrieve — callers must time-box it.
40
- */
41
- connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
42
33
  removeListener(event: 'stateChange', listener: (state: string) => void): void;
43
34
  removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
44
35
  }