@onekeyfe/hd-transport-electron 1.2.2-alpha.14 → 1.2.2-alpha.15

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-de8d60e9.js'); });
35
+ const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-95c5cf0d.js'); });
36
36
  setupNobleBleHandlers(webContents);
37
37
  });
38
38
  }
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var index = require('./index-ef6298f9.js');
5
+ var index = require('./index-00297050.js');
6
6
 
7
7
 
8
8
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-ef6298f9.js');
3
+ var index = require('./index-00297050.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -96,20 +96,10 @@ function softRefreshSubscription(params) {
96
96
  }
97
97
 
98
98
  const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
99
- const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10000;
99
+ const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
100
100
 
101
101
  let noble = null;
102
102
  let logger = null;
103
- function createNobleBleConnectionError(error, messagePrefix = '') {
104
- const errorMessage = error.message;
105
- if (hdShared.isBleStaleBondErrorText(errorMessage)) {
106
- const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
107
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
108
- nativeErrorMessage,
109
- });
110
- }
111
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
112
- }
113
103
  const bluetoothState = {
114
104
  available: false,
115
105
  unsupported: false,
@@ -934,14 +924,9 @@ function forceReconnectPeripheral(peripheral, deviceId) {
934
924
  callback();
935
925
  }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
936
926
  }
937
- yield new Promise((resolve, reject) => {
938
- peripheral.connect(error => {
939
- if (error) {
940
- reject(createNobleBleConnectionError(error));
941
- return;
942
- }
943
- resolve();
944
- });
927
+ yield runBleCallbackOperation(callback => peripheral.connect(callback), {
928
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
929
+ timeoutBehavior: 'reject',
945
930
  });
946
931
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
947
932
  connectedDevices.set(deviceId, peripheral);
@@ -964,7 +949,7 @@ function freshScanAndDiscover(deviceId, webContents) {
964
949
  yield new Promise((resolve, reject) => {
965
950
  freshPeripheral.connect((error) => {
966
951
  if (error) {
967
- reject(createNobleBleConnectionError(error, 'Fresh peripheral connection failed: '));
952
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Fresh peripheral connection failed: ${error.message}`));
968
953
  }
969
954
  else {
970
955
  connectedDevices.set(deviceId, freshPeripheral);
@@ -1025,9 +1010,6 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
1025
1010
  yield forceReconnectPeripheral(peripheral, deviceId);
1026
1011
  }
1027
1012
  catch (resetError) {
1028
- if (hdShared.isBleStaleBondHardwareError(resetError)) {
1029
- throw resetError;
1030
- }
1031
1013
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
1032
1014
  }
1033
1015
  setupDisconnectListener(peripheral, deviceId, webContents);
@@ -1195,11 +1177,27 @@ function connectDevice(deviceId, webContents) {
1195
1177
  return;
1196
1178
  }
1197
1179
  return new Promise((resolve, reject) => {
1180
+ let connectionTimedOut = false;
1181
+ const timeout = setTimeout(() => {
1182
+ connectionTimedOut = true;
1183
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1184
+ }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
1198
1185
  const connectedPeripheral = peripheral;
1199
1186
  connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
1187
+ clearTimeout(timeout);
1188
+ if (connectionTimedOut) {
1189
+ if (!error) {
1190
+ try {
1191
+ connectedPeripheral.disconnect(() => undefined);
1192
+ }
1193
+ catch (_a) {
1194
+ }
1195
+ }
1196
+ return;
1197
+ }
1200
1198
  if (error) {
1201
1199
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
1202
- reject(createNobleBleConnectionError(error));
1200
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
1203
1201
  return;
1204
1202
  }
1205
1203
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
@@ -1319,7 +1317,7 @@ function subscribeNotifications(deviceId, callback) {
1319
1317
  timeoutBehavior: 'resolve',
1320
1318
  });
1321
1319
  yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1322
- timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1320
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1323
1321
  timeoutBehavior: 'reject',
1324
1322
  });
1325
1323
  notifyCharacteristic.on('data', (data) => {
@@ -1471,6 +1469,5 @@ function setupNobleBleHandlers(webContents) {
1471
1469
  }
1472
1470
  }
1473
1471
 
1474
- exports.createNobleBleConnectionError = createNobleBleConnectionError;
1475
1472
  exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
1476
1473
  exports.setupNobleBleHandlers = setupNobleBleHandlers;
@@ -1,6 +1,5 @@
1
1
  import type { WebContents } from 'electron';
2
2
  import type { NobleBleWriteOptions } from './types/desktop-api';
3
- export declare function createNobleBleConnectionError(error: Error, messagePrefix?: string): import("@onekeyfe/hd-shared").HardwareError;
4
3
  export declare function resolveNobleBleWritePacingDelay(options?: NobleBleWriteOptions): number;
5
4
  export declare function setupNobleBleHandlers(webContents: WebContents): void;
6
5
  //# sourceMappingURL=noble-ble-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAOhE,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,SAAK,+CAc7E;AA+ED,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AAuuDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAkMpE"}
1
+ {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAoFhE,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AAyvDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAkMpE"}
@@ -1,3 +1,3 @@
1
1
  export declare const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
2
- export declare const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10000;
2
+ export declare const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
3
3
  //# sourceMappingURL=noble-ble-timeouts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"noble-ble-timeouts.d.ts","sourceRoot":"","sources":["../src/noble-ble-timeouts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,OAAQ,CAAC;AACxD,eAAO,MAAM,8BAA8B,QAAS,CAAC"}
1
+ {"version":3,"file":"noble-ble-timeouts.d.ts","sourceRoot":"","sources":["../src/noble-ble-timeouts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,OAAQ,CAAC;AACxD,eAAO,MAAM,+BAA+B,QAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-electron",
3
- "version": "1.2.2-alpha.14",
3
+ "version": "1.2.2-alpha.15",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -25,9 +25,9 @@
25
25
  "electron-log": ">=4.0.0"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-core": "1.2.2-alpha.14",
29
- "@onekeyfe/hd-shared": "1.2.2-alpha.14",
30
- "@onekeyfe/hd-transport": "1.2.2-alpha.14",
28
+ "@onekeyfe/hd-core": "1.2.2-alpha.15",
29
+ "@onekeyfe/hd-shared": "1.2.2-alpha.15",
30
+ "@onekeyfe/hd-transport": "1.2.2-alpha.15",
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": "1b3bf0576a53bedbc5f4a82308c48e11ffc00b16"
39
+ "gitHead": "ea66eff5817385ee2144d8e037c385342e44e21f"
40
40
  }
@@ -1,8 +1,8 @@
1
1
  import { EventEmitter } from 'events';
2
- import { EOneKeyBleMessageKeys, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { EOneKeyBleMessageKeys } from '@onekeyfe/hd-shared';
3
3
 
4
4
  import {
5
- NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
5
+ NOBLE_BLE_CONNECTION_TIMEOUT_MS,
6
6
  NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
7
7
  } from '../noble-ble-timeouts';
8
8
 
@@ -26,40 +26,9 @@ describe('Electron Noble BLE device discovery', () => {
26
26
  jest.clearAllMocks();
27
27
  });
28
28
 
29
- test('bounds targeted scans and subscription callbacks independently', () => {
29
+ test('allows enough time for a slow targeted scan and connection', () => {
30
30
  expect(NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS).toBe(5_000);
31
- expect(NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS).toBe(10_000);
32
- });
33
-
34
- test('maps macOS stale pairing failures without reclassifying generic connection errors', async () => {
35
- const { createNobleBleConnectionError } = await import('../noble-ble-handler');
36
-
37
- const staleBondError = createNobleBleConnectionError(
38
- new Error('CBErrorDomain:14 Peer removed pairing information on the device side')
39
- );
40
- expect(staleBondError).toMatchObject({
41
- errorCode: HardwareErrorCode.BleBondInvalid,
42
- params: {
43
- nativeErrorMessage: 'CBErrorDomain:14 Peer removed pairing information on the device side',
44
- },
45
- });
46
- expect(staleBondError.message).toContain(
47
- 'CBErrorDomain:14 Peer removed pairing information on the device side'
48
- );
49
- expect(createNobleBleConnectionError(new Error('Encryption is insufficient'))).toMatchObject({
50
- errorCode: HardwareErrorCode.BleBondInvalid,
51
- params: {
52
- nativeErrorMessage: 'Encryption is insufficient',
53
- },
54
- });
55
- expect(
56
- createNobleBleConnectionError(new Error('CBATTErrorDomain:14 localized native message'))
57
- ).toMatchObject({
58
- errorCode: HardwareErrorCode.BleBondInvalid,
59
- });
60
- expect(createNobleBleConnectionError(new Error('connection failed'))).toMatchObject({
61
- errorCode: HardwareErrorCode.BleConnectedError,
62
- });
31
+ expect(NOBLE_BLE_CONNECTION_TIMEOUT_MS).toBe(10_000);
63
32
  });
64
33
 
65
34
  test('keeps safe pacing by default and allows an explicit high-throughput bypass', async () => {
@@ -211,33 +180,75 @@ describe('Electron Noble BLE device discovery', () => {
211
180
  expect(peripheral.connect).toHaveBeenCalledTimes(1);
212
181
  });
213
182
 
214
- test('settles a pending Noble connect with the native disconnect error', () => {
215
- // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
216
- const Noble = require('@stoprocent/noble/lib/noble');
217
- const bindings = Object.assign(new EventEmitter(), {
218
- connect: jest.fn(),
183
+ test('disconnects a connection callback that arrives after timeout', async () => {
184
+ jest.useFakeTimers({ doNotFake: ['performance'] });
185
+
186
+ const handlers = new Map<string, IpcHandler>();
187
+ const ipcMain = {
188
+ handle: jest.fn((channel: string, handler: IpcHandler) => {
189
+ handlers.set(channel, handler);
190
+ }),
191
+ removeHandler: jest.fn((channel: string) => {
192
+ handlers.delete(channel);
193
+ }),
194
+ };
195
+ const noble = new EventEmitter() as EventEmitter & {
196
+ state: string;
197
+ startScanning: jest.Mock;
198
+ stopScanning: jest.Mock;
199
+ };
200
+ let connectCallback: ((error?: Error) => void) | undefined;
201
+ let resolveConnectStarted = () => undefined;
202
+ const connectStarted = new Promise<void>(resolve => {
203
+ resolveConnectStarted = resolve;
219
204
  });
220
- const noble = new Noble(bindings);
221
- noble._registerListeners();
222
- const peripheral = noble._createPeripheral(
223
- 'aabbccdd',
224
- '',
225
- 'unknown',
226
- true,
227
- { localName: 'Pro2 A1B2', serviceUuids: ['0001'] },
228
- -50,
229
- false
205
+ const peripheral = Object.assign(
206
+ new EventEmitter(),
207
+ createPeripheral('slow-device', 'Pro2 A1B2'),
208
+ {
209
+ connect: jest.fn((callback: (error?: Error) => void) => {
210
+ connectCallback = callback;
211
+ resolveConnectStarted();
212
+ }),
213
+ disconnect: jest.fn((callback: () => void) => callback()),
214
+ }
230
215
  );
231
- const connectCallback = jest.fn();
216
+ noble.state = 'poweredOn';
217
+ noble.startScanning = jest.fn((_services, _duplicates, callback) => {
218
+ callback?.();
219
+ noble.emit('discover', peripheral);
220
+ });
221
+ noble.stopScanning = jest.fn(callback => callback?.());
232
222
 
233
- peripheral.connect(connectCallback);
234
- const nativeError = new Error(
235
- 'CBATTErrorDomain:14 Peer removed pairing information on the device side'
236
- );
237
- bindings.emit('disconnect', 'aabbccdd', nativeError);
223
+ jest.doMock('@stoprocent/noble', () => noble);
224
+ jest.doMock('electron', () => ({ ipcMain }));
225
+ jest.doMock('electron-log', () => ({
226
+ info: jest.fn(),
227
+ debug: jest.fn(),
228
+ error: jest.fn(),
229
+ }));
238
230
 
239
- expect(connectCallback).toHaveBeenCalledWith(nativeError);
240
- expect(peripheral.state).toBe('disconnected');
231
+ const { setupNobleBleHandlers } = await import('../noble-ble-handler');
232
+ setupNobleBleHandlers({
233
+ on: jest.fn(),
234
+ send: jest.fn(),
235
+ } as unknown as WebContents);
236
+
237
+ const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
238
+ if (!connect) {
239
+ throw new Error('Electron Noble BLE connect handler was not registered');
240
+ }
241
+
242
+ const connectPromise = Promise.resolve(connect(undefined, 'slow-device'));
243
+ await connectStarted;
244
+ expect(peripheral.connect).toHaveBeenCalledTimes(1);
245
+
246
+ jest.advanceTimersByTime(NOBLE_BLE_CONNECTION_TIMEOUT_MS);
247
+ await expect(connectPromise).rejects.toThrow('Connection timeout');
248
+
249
+ connectCallback?.();
250
+ await Promise.resolve();
251
+ expect(peripheral.disconnect).toHaveBeenCalledTimes(1);
241
252
  });
242
253
 
243
254
  test('enumerates a Pro2 communication advertisement after Find My changes its name', async () => {
@@ -10,14 +10,11 @@ import {
10
10
  EOneKeyBleMessageKeys,
11
11
  ERRORS,
12
12
  HardwareErrorCode,
13
- HardwareErrorCodeMessage,
14
13
  ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
15
14
  ONEKEY_SERVICE_UUID,
16
15
  ONEKEY_WRITE_CHARACTERISTIC_UUID,
17
16
  createKnownBleUuidAliases,
18
17
  hasOnekeyCommunicationService,
19
- isBleStaleBondErrorText,
20
- isBleStaleBondHardwareError,
21
18
  isOnekeyBluetoothDevice,
22
19
  isPro2FamilyBleName,
23
20
  matchesKnownBleUuid,
@@ -29,7 +26,7 @@ import { resolveBlePacketCapacity, resolveNobleAttMtu } from './ble-packet-capac
29
26
  import { safeLog } from './types/noble-extended';
30
27
  import { runBleCallbackOperation, softRefreshSubscription } from './ble-ops';
31
28
  import {
32
- NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
29
+ NOBLE_BLE_CONNECTION_TIMEOUT_MS,
33
30
  NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
34
31
  } from './noble-ble-timeouts';
35
32
 
@@ -42,22 +39,6 @@ import type { CharacteristicPair, DeviceInfo, Logger, NobleModule } from './type
42
39
  let noble: NobleModule | null = null;
43
40
  let logger: Logger | null = null;
44
41
 
45
- export function createNobleBleConnectionError(error: Error, messagePrefix = '') {
46
- const errorMessage = error.message;
47
- if (isBleStaleBondErrorText(errorMessage)) {
48
- const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
49
- return ERRORS.TypedError(
50
- HardwareErrorCode.BleBondInvalid,
51
- `${HardwareErrorCodeMessage[HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`,
52
- {
53
- nativeErrorMessage,
54
- }
55
- );
56
- }
57
-
58
- return ERRORS.TypedError(HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
59
- }
60
-
61
42
  // Bluetooth state management
62
43
  const bluetoothState: {
63
44
  available: boolean;
@@ -1271,14 +1252,9 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
1271
1252
  }
1272
1253
 
1273
1254
  // Step 3: Re-establish connection
1274
- await new Promise<void>((resolve, reject) => {
1275
- peripheral.connect(error => {
1276
- if (error) {
1277
- reject(createNobleBleConnectionError(error));
1278
- return;
1279
- }
1280
- resolve();
1281
- });
1255
+ await runBleCallbackOperation(callback => peripheral.connect(callback), {
1256
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1257
+ timeoutBehavior: 'reject',
1282
1258
  });
1283
1259
  logger?.info('[NobleBLE] Force reconnect successful');
1284
1260
  connectedDevices.set(deviceId, peripheral);
@@ -1320,7 +1296,12 @@ async function freshScanAndDiscover(
1320
1296
  await new Promise<void>((resolve, reject) => {
1321
1297
  freshPeripheral.connect((error: Error | undefined) => {
1322
1298
  if (error) {
1323
- reject(createNobleBleConnectionError(error, 'Fresh peripheral connection failed: '));
1299
+ reject(
1300
+ ERRORS.TypedError(
1301
+ HardwareErrorCode.BleConnectedError,
1302
+ `Fresh peripheral connection failed: ${error.message}`
1303
+ )
1304
+ );
1324
1305
  } else {
1325
1306
  connectedDevices.set(deviceId, freshPeripheral);
1326
1307
  resolve();
@@ -1433,9 +1414,6 @@ async function setupConnectionAndDiscoverServices(
1433
1414
  try {
1434
1415
  await forceReconnectPeripheral(peripheral, deviceId);
1435
1416
  } catch (resetError) {
1436
- if (isBleStaleBondHardwareError(resetError)) {
1437
- throw resetError;
1438
- }
1439
1417
  // A failed reset must not abort the attempt: discovery on the existing
1440
1418
  // link, then the fresh-scan fallback, still have a chance to recover.
1441
1419
  logger?.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
@@ -1688,12 +1666,33 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1688
1666
  }
1689
1667
 
1690
1668
  return new Promise((resolve, reject) => {
1669
+ let connectionTimedOut = false;
1670
+ const timeout = setTimeout(() => {
1671
+ connectionTimedOut = true;
1672
+ reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
1673
+ }, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
1674
+
1691
1675
  // TypeScript type assertion - peripheral is guaranteed to be defined at this point
1692
1676
  const connectedPeripheral = peripheral as Peripheral;
1693
1677
  connectedPeripheral.connect(async (error: Error | undefined) => {
1678
+ clearTimeout(timeout);
1679
+
1680
+ // Noble may invoke the callback after the SDK timed out and released the request.
1681
+ // Ignore it to avoid initializing disposed commands or leaving an orphaned connection.
1682
+ if (connectionTimedOut) {
1683
+ if (!error) {
1684
+ try {
1685
+ connectedPeripheral.disconnect(() => undefined);
1686
+ } catch {
1687
+ // Best-effort cleanup only.
1688
+ }
1689
+ }
1690
+ return;
1691
+ }
1692
+
1694
1693
  if (error) {
1695
1694
  logger?.error('[NobleBLE] Connection failed:', error);
1696
- reject(createNobleBleConnectionError(error));
1695
+ reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
1697
1696
  return;
1698
1697
  }
1699
1698
 
@@ -1876,7 +1875,7 @@ async function subscribeNotifications(
1876
1875
  timeoutBehavior: 'resolve',
1877
1876
  });
1878
1877
  await runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1879
- timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1878
+ timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1880
1879
  timeoutBehavior: 'reject',
1881
1880
  });
1882
1881
 
@@ -1,2 +1,2 @@
1
1
  export const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5_000;
2
- export const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10_000;
2
+ export const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10_000;