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

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-95c5cf0d.js'); });
35
+ const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-0b455c4f.js'); });
36
36
  setupNobleBleHandlers(webContents);
37
37
  });
38
38
  }
package/dist/index.d.ts CHANGED
@@ -37,6 +37,17 @@ interface Logger {
37
37
  interface NobleBleWriteOptions {
38
38
  pacingDelayMs?: number;
39
39
  }
40
+ type NobleBleIpcErrorPayload = {
41
+ name: string;
42
+ message: string;
43
+ errorCode: number;
44
+ params?: unknown;
45
+ };
46
+ type NobleBleIpcErrorResponse = {
47
+ type: 'NobleBleIpcError';
48
+ success: false;
49
+ error: NobleBleIpcErrorPayload;
50
+ };
40
51
  interface NobleBleAPI {
41
52
  enumerate: () => Promise<{
42
53
  id: string;
@@ -82,4 +93,4 @@ interface DesktopAPI {
82
93
 
83
94
  declare function initNobleBleSupport(webContents: WebContents): Promise<void>;
84
95
 
85
- export { CharacteristicPair, DesktopAPI, DeviceInfo, Logger, NobleBleAPI, NobleBleWriteOptions, NobleModule, initNobleBleSupport };
96
+ export { CharacteristicPair, DesktopAPI, DeviceInfo, Logger, NobleBleAPI, NobleBleIpcErrorPayload, NobleBleIpcErrorResponse, NobleBleWriteOptions, NobleModule, initNobleBleSupport };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,cAAc,SAAS,CAAC;AAGxB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,WAAW,iBAGjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,cAAc,SAAS,CAAC;AAGxB,YAAY,EACV,UAAU,EACV,WAAW,EACX,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAE7B,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,WAAW,iBAGjE"}
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-00297050.js');
5
+ var index = require('./index-d51c7da6.js');
6
6
 
7
7
 
8
8
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-00297050.js');
3
+ var index = require('./index-d51c7da6.js');
4
4
  var hdShared = require('@onekeyfe/hd-shared');
5
5
  var pRetry = require('p-retry');
6
6
 
@@ -96,10 +96,44 @@ function softRefreshSubscription(params) {
96
96
  }
97
97
 
98
98
  const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
99
- const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
99
+ const NOBLE_BLE_SUBSCRIBE_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
+ const isInvalidMacOsBond = error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain';
106
+ if (isInvalidMacOsBond) {
107
+ const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
108
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
109
+ nativeErrorMessage,
110
+ });
111
+ }
112
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
113
+ }
114
+ function createNobleBleIpcErrorResponse(error) {
115
+ const candidate = error;
116
+ const errorCode = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.errorCode) === 'number' ? candidate.errorCode : hdShared.HardwareErrorCode.UnknownError;
117
+ const message = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.message) === 'string' ? candidate.message : String(error !== null && error !== void 0 ? error : 'Unknown error');
118
+ const name = typeof (candidate === null || candidate === void 0 ? void 0 : candidate.name) === 'string' ? candidate.name : 'Error';
119
+ let params;
120
+ if ((candidate === null || candidate === void 0 ? void 0 : candidate.params) !== undefined) {
121
+ try {
122
+ const serializedParams = JSON.stringify(candidate.params);
123
+ params = serializedParams === undefined ? undefined : JSON.parse(serializedParams);
124
+ }
125
+ catch (_a) {
126
+ params = undefined;
127
+ }
128
+ }
129
+ return {
130
+ type: 'NobleBleIpcError',
131
+ success: false,
132
+ error: Object.assign({ name,
133
+ message,
134
+ errorCode }, (params !== undefined ? { params } : {})),
135
+ };
136
+ }
103
137
  const bluetoothState = {
104
138
  available: false,
105
139
  unsupported: false,
@@ -924,9 +958,14 @@ function forceReconnectPeripheral(peripheral, deviceId) {
924
958
  callback();
925
959
  }), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
926
960
  }
927
- yield runBleCallbackOperation(callback => peripheral.connect(callback), {
928
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
929
- timeoutBehavior: 'reject',
961
+ yield new Promise((resolve, reject) => {
962
+ peripheral.connect(error => {
963
+ if (error) {
964
+ reject(createNobleBleConnectionError(error));
965
+ return;
966
+ }
967
+ resolve();
968
+ });
930
969
  });
931
970
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
932
971
  connectedDevices.set(deviceId, peripheral);
@@ -949,7 +988,7 @@ function freshScanAndDiscover(deviceId, webContents) {
949
988
  yield new Promise((resolve, reject) => {
950
989
  freshPeripheral.connect((error) => {
951
990
  if (error) {
952
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Fresh peripheral connection failed: ${error.message}`));
991
+ reject(createNobleBleConnectionError(error, 'Fresh peripheral connection failed: '));
953
992
  }
954
993
  else {
955
994
  connectedDevices.set(deviceId, freshPeripheral);
@@ -1010,6 +1049,9 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
1010
1049
  yield forceReconnectPeripheral(peripheral, deviceId);
1011
1050
  }
1012
1051
  catch (resetError) {
1052
+ if (hdShared.isBleStaleBondHardwareError(resetError)) {
1053
+ throw resetError;
1054
+ }
1013
1055
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
1014
1056
  }
1015
1057
  setupDisconnectListener(peripheral, deviceId, webContents);
@@ -1177,27 +1219,11 @@ function connectDevice(deviceId, webContents) {
1177
1219
  return;
1178
1220
  }
1179
1221
  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);
1185
1222
  const connectedPeripheral = peripheral;
1186
1223
  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
- }
1198
1224
  if (error) {
1199
1225
  logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
1200
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
1226
+ reject(createNobleBleConnectionError(error));
1201
1227
  return;
1202
1228
  }
1203
1229
  logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connected to device:', deviceId);
@@ -1317,7 +1343,7 @@ function subscribeNotifications(deviceId, callback) {
1317
1343
  timeoutBehavior: 'resolve',
1318
1344
  });
1319
1345
  yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1320
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1346
+ timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1321
1347
  timeoutBehavior: 'reject',
1322
1348
  });
1323
1349
  notifyCharacteristic.on('data', (data) => {
@@ -1349,7 +1375,14 @@ function setupNobleBleHandlers(webContents) {
1349
1375
  const { ipcMain } = require('electron');
1350
1376
  const handle = (channel, listener) => {
1351
1377
  ipcMain.removeHandler(channel);
1352
- ipcMain.handle(channel, listener);
1378
+ ipcMain.handle(channel, (...args) => index.__awaiter(this, void 0, void 0, function* () {
1379
+ try {
1380
+ return yield Promise.resolve(listener(...args));
1381
+ }
1382
+ catch (error) {
1383
+ return createNobleBleIpcErrorResponse(error);
1384
+ }
1385
+ }));
1353
1386
  };
1354
1387
  safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
1355
1388
  handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
@@ -1469,5 +1502,7 @@ function setupNobleBleHandlers(webContents) {
1469
1502
  }
1470
1503
  }
1471
1504
 
1505
+ exports.createNobleBleConnectionError = createNobleBleConnectionError;
1506
+ exports.createNobleBleIpcErrorResponse = createNobleBleIpcErrorResponse;
1472
1507
  exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
1473
1508
  exports.setupNobleBleHandlers = setupNobleBleHandlers;
@@ -1,5 +1,12 @@
1
1
  import type { WebContents } from 'electron';
2
- import type { NobleBleWriteOptions } from './types/desktop-api';
2
+ import type { NobleBleIpcErrorResponse, NobleBleWriteOptions } from './types/desktop-api';
3
+ type NobleBleNativeError = Error & {
4
+ nativeErrorCode?: number;
5
+ nativeErrorDomain?: string;
6
+ };
7
+ export declare function createNobleBleConnectionError(error: NobleBleNativeError, messagePrefix?: string): import("@onekeyfe/hd-shared").HardwareError;
8
+ export declare function createNobleBleIpcErrorResponse(error: unknown): NobleBleIpcErrorResponse;
3
9
  export declare function resolveNobleBleWritePacingDelay(options?: NobleBleWriteOptions): number;
4
10
  export declare function setupNobleBleHandlers(webContents: WebContents): void;
11
+ export {};
5
12
  //# 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":"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
+ {"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAO1F,KAAK,mBAAmB,GAAG,KAAK,GAAG;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,mBAAmB,EAAE,aAAa,SAAK,+CAgB3F;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAgCvF;AA+ED,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AAuuDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAwMpE"}
@@ -1,3 +1,3 @@
1
1
  export declare const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
2
- export declare const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
2
+ export declare const NOBLE_BLE_SUBSCRIBE_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,+BAA+B,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,8BAA8B,QAAS,CAAC"}
@@ -2,6 +2,17 @@ import type { EBleDisconnectReason } from '@onekeyfe/hd-shared';
2
2
  export interface NobleBleWriteOptions {
3
3
  pacingDelayMs?: number;
4
4
  }
5
+ export type NobleBleIpcErrorPayload = {
6
+ name: string;
7
+ message: string;
8
+ errorCode: number;
9
+ params?: unknown;
10
+ };
11
+ export type NobleBleIpcErrorResponse = {
12
+ type: 'NobleBleIpcError';
13
+ success: false;
14
+ error: NobleBleIpcErrorPayload;
15
+ };
5
16
  export interface NobleBleAPI {
6
17
  enumerate: () => Promise<{
7
18
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,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,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,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,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAOvF,oBAAoB,EAAE,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;KAAE,KAAK,IAAI,KACpF,MAAM,IAAI,CAAC;IAChB,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":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;CAChC,CAAC;AAEF,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,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,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,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAOvF,oBAAoB,EAAE,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;KAAE,KAAK,IAAI,KACpF,MAAM,IAAI,CAAC;IAChB,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-electron",
3
- "version": "1.2.2-alpha.15",
3
+ "version": "1.2.2-alpha.17",
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.15",
29
- "@onekeyfe/hd-shared": "1.2.2-alpha.15",
30
- "@onekeyfe/hd-transport": "1.2.2-alpha.15",
28
+ "@onekeyfe/hd-core": "1.2.2-alpha.17",
29
+ "@onekeyfe/hd-shared": "1.2.2-alpha.17",
30
+ "@onekeyfe/hd-transport": "1.2.2-alpha.17",
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": "ea66eff5817385ee2144d8e037c385342e44e21f"
39
+ "gitHead": "b6c48b842d1c325dcd5ce42c03a07ee4377002dd"
40
40
  }
@@ -1,8 +1,8 @@
1
1
  import { EventEmitter } from 'events';
2
- import { EOneKeyBleMessageKeys } from '@onekeyfe/hd-shared';
2
+ import { EOneKeyBleMessageKeys, HardwareErrorCode } from '@onekeyfe/hd-shared';
3
3
 
4
4
  import {
5
- NOBLE_BLE_CONNECTION_TIMEOUT_MS,
5
+ NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
6
6
  NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
7
7
  } from '../noble-ble-timeouts';
8
8
 
@@ -26,9 +26,93 @@ describe('Electron Noble BLE device discovery', () => {
26
26
  jest.clearAllMocks();
27
27
  });
28
28
 
29
- test('allows enough time for a slow targeted scan and connection', () => {
29
+ test('bounds targeted scans and subscription callbacks independently', () => {
30
30
  expect(NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS).toBe(5_000);
31
- expect(NOBLE_BLE_CONNECTION_TIMEOUT_MS).toBe(10_000);
31
+ expect(NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS).toBe(10_000);
32
+ });
33
+
34
+ test('maps structured macOS stale pairing failures without parsing localized text', async () => {
35
+ const { createNobleBleConnectionError } = await import('../noble-ble-handler');
36
+
37
+ const staleBondError = createNobleBleConnectionError(
38
+ Object.assign(new Error('Peer removed pairing information on the device side'), {
39
+ nativeErrorCode: 14,
40
+ nativeErrorDomain: 'CBErrorDomain',
41
+ })
42
+ );
43
+ expect(staleBondError).toMatchObject({
44
+ errorCode: HardwareErrorCode.BleBondInvalid,
45
+ params: {
46
+ nativeErrorMessage: 'Peer removed pairing information on the device side',
47
+ },
48
+ });
49
+ expect(staleBondError.message).toContain('Peer removed pairing information on the device side');
50
+ expect(createNobleBleConnectionError(new Error('Encryption is insufficient'))).toMatchObject({
51
+ errorCode: HardwareErrorCode.BleConnectedError,
52
+ });
53
+ expect(
54
+ createNobleBleConnectionError(
55
+ Object.assign(new Error('localized native message'), {
56
+ nativeErrorCode: 14,
57
+ nativeErrorDomain: 'CBATTErrorDomain',
58
+ })
59
+ )
60
+ ).toMatchObject({
61
+ errorCode: HardwareErrorCode.BleConnectedError,
62
+ });
63
+ expect(createNobleBleConnectionError(new Error('connection failed'))).toMatchObject({
64
+ errorCode: HardwareErrorCode.BleConnectedError,
65
+ });
66
+ });
67
+
68
+ test('serializes HardwareError fields for the Noble IPC boundary', async () => {
69
+ const { createNobleBleIpcErrorResponse } = await import('../noble-ble-handler');
70
+
71
+ expect(
72
+ createNobleBleIpcErrorResponse({
73
+ name: 'HardwareError',
74
+ message: 'Bluetooth pairing information is no longer valid',
75
+ errorCode: HardwareErrorCode.BleBondInvalid,
76
+ params: { nativeErrorMessage: 'native message' },
77
+ })
78
+ ).toEqual({
79
+ type: 'NobleBleIpcError',
80
+ success: false,
81
+ error: {
82
+ name: 'HardwareError',
83
+ message: 'Bluetooth pairing information is no longer valid',
84
+ errorCode: HardwareErrorCode.BleBondInvalid,
85
+ params: { nativeErrorMessage: 'native message' },
86
+ },
87
+ });
88
+
89
+ expect(createNobleBleIpcErrorResponse(new Error('untyped failure'))).toEqual({
90
+ type: 'NobleBleIpcError',
91
+ success: false,
92
+ error: {
93
+ name: 'Error',
94
+ message: 'untyped failure',
95
+ errorCode: HardwareErrorCode.UnknownError,
96
+ },
97
+ });
98
+
99
+ const circularParams: { self?: unknown } = {};
100
+ circularParams.self = circularParams;
101
+ expect(
102
+ createNobleBleIpcErrorResponse({
103
+ message: 'failure with unsafe params',
104
+ errorCode: HardwareErrorCode.BleConnectedError,
105
+ params: circularParams,
106
+ })
107
+ ).toEqual({
108
+ type: 'NobleBleIpcError',
109
+ success: false,
110
+ error: {
111
+ name: 'Error',
112
+ message: 'failure with unsafe params',
113
+ errorCode: HardwareErrorCode.BleConnectedError,
114
+ },
115
+ });
32
116
  });
33
117
 
34
118
  test('keeps safe pacing by default and allows an explicit high-throughput bypass', async () => {
@@ -176,79 +260,45 @@ describe('Electron Noble BLE device discovery', () => {
176
260
  expect(peripheral.connect).not.toHaveBeenCalled();
177
261
 
178
262
  stopScanningCallback?.();
179
- await expect(connectPromise).rejects.toThrow('expected test connection failure');
263
+ await expect(connectPromise).resolves.toEqual({
264
+ type: 'NobleBleIpcError',
265
+ success: false,
266
+ error: {
267
+ name: 'HardwareError',
268
+ message: 'expected test connection failure',
269
+ errorCode: HardwareErrorCode.BleConnectedError,
270
+ },
271
+ });
180
272
  expect(peripheral.connect).toHaveBeenCalledTimes(1);
181
273
  });
182
274
 
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;
275
+ test('settles a pending Noble connect with the native disconnect error', () => {
276
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
277
+ const Noble = require('@stoprocent/noble/lib/noble');
278
+ const bindings = Object.assign(new EventEmitter(), {
279
+ connect: jest.fn(),
204
280
  });
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
- }
281
+ const noble = new Noble(bindings);
282
+ noble._registerListeners();
283
+ const peripheral = noble._createPeripheral(
284
+ 'aabbccdd',
285
+ '',
286
+ 'unknown',
287
+ true,
288
+ { localName: 'Pro2 A1B2', serviceUuids: ['0001'] },
289
+ -50,
290
+ false
215
291
  );
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?.());
292
+ const connectCallback = jest.fn();
222
293
 
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
- }));
230
-
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');
294
+ peripheral.connect(connectCallback);
295
+ const nativeError = new Error(
296
+ 'CBErrorDomain:14 Peer removed pairing information on the device side'
297
+ );
298
+ bindings.emit('disconnect', 'aabbccdd', nativeError);
248
299
 
249
- connectCallback?.();
250
- await Promise.resolve();
251
- expect(peripheral.disconnect).toHaveBeenCalledTimes(1);
300
+ expect(connectCallback).toHaveBeenCalledWith(nativeError);
301
+ expect(peripheral.state).toBe('disconnected');
252
302
  });
253
303
 
254
304
  test('enumerates a Pro2 communication advertisement after Find My changes its name', async () => {
package/src/index.ts CHANGED
@@ -3,7 +3,13 @@ import type { WebContents } from 'electron';
3
3
  export * from './types';
4
4
 
5
5
  // Export Desktop API types for other packages to reuse
6
- export type { DesktopAPI, NobleBleAPI, NobleBleWriteOptions } from './types/desktop-api';
6
+ export type {
7
+ DesktopAPI,
8
+ NobleBleAPI,
9
+ NobleBleIpcErrorPayload,
10
+ NobleBleIpcErrorResponse,
11
+ NobleBleWriteOptions,
12
+ } from './types/desktop-api';
7
13
 
8
14
  export async function initNobleBleSupport(webContents: WebContents) {
9
15
  const { setupNobleBleHandlers } = await import('./noble-ble-handler');
@@ -10,11 +10,13 @@ import {
10
10
  EOneKeyBleMessageKeys,
11
11
  ERRORS,
12
12
  HardwareErrorCode,
13
+ HardwareErrorCodeMessage,
13
14
  ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
14
15
  ONEKEY_SERVICE_UUID,
15
16
  ONEKEY_WRITE_CHARACTERISTIC_UUID,
16
17
  createKnownBleUuidAliases,
17
18
  hasOnekeyCommunicationService,
19
+ isBleStaleBondHardwareError,
18
20
  isOnekeyBluetoothDevice,
19
21
  isPro2FamilyBleName,
20
22
  matchesKnownBleUuid,
@@ -26,19 +28,76 @@ import { resolveBlePacketCapacity, resolveNobleAttMtu } from './ble-packet-capac
26
28
  import { safeLog } from './types/noble-extended';
27
29
  import { runBleCallbackOperation, softRefreshSubscription } from './ble-ops';
28
30
  import {
29
- NOBLE_BLE_CONNECTION_TIMEOUT_MS,
31
+ NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
30
32
  NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS,
31
33
  } from './noble-ble-timeouts';
32
34
 
33
35
  import type { IpcMain, IpcMainInvokeEvent, WebContents } from 'electron';
34
36
  import type { Characteristic, Peripheral, Service } from '@stoprocent/noble';
35
- import type { NobleBleWriteOptions } from './types/desktop-api';
37
+ import type { NobleBleIpcErrorResponse, NobleBleWriteOptions } from './types/desktop-api';
36
38
  import type { CharacteristicPair, DeviceInfo, Logger, NobleModule } from './types/noble-extended';
37
39
 
38
40
  // Noble will be dynamically imported to avoid bundling issues
39
41
  let noble: NobleModule | null = null;
40
42
  let logger: Logger | null = null;
41
43
 
44
+ type NobleBleNativeError = Error & {
45
+ nativeErrorCode?: number;
46
+ nativeErrorDomain?: string;
47
+ };
48
+
49
+ export function createNobleBleConnectionError(error: NobleBleNativeError, messagePrefix = '') {
50
+ const errorMessage = error.message;
51
+ const isInvalidMacOsBond =
52
+ error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain';
53
+ if (isInvalidMacOsBond) {
54
+ const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
55
+ return ERRORS.TypedError(
56
+ HardwareErrorCode.BleBondInvalid,
57
+ `${HardwareErrorCodeMessage[HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`,
58
+ {
59
+ nativeErrorMessage,
60
+ }
61
+ );
62
+ }
63
+
64
+ return ERRORS.TypedError(HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
65
+ }
66
+
67
+ export function createNobleBleIpcErrorResponse(error: unknown): NobleBleIpcErrorResponse {
68
+ const candidate = error as {
69
+ errorCode?: unknown;
70
+ message?: unknown;
71
+ name?: unknown;
72
+ params?: unknown;
73
+ };
74
+ const errorCode =
75
+ typeof candidate?.errorCode === 'number' ? candidate.errorCode : HardwareErrorCode.UnknownError;
76
+ const message =
77
+ typeof candidate?.message === 'string' ? candidate.message : String(error ?? 'Unknown error');
78
+ const name = typeof candidate?.name === 'string' ? candidate.name : 'Error';
79
+ let params: unknown;
80
+ if (candidate?.params !== undefined) {
81
+ try {
82
+ const serializedParams = JSON.stringify(candidate.params);
83
+ params = serializedParams === undefined ? undefined : JSON.parse(serializedParams);
84
+ } catch {
85
+ params = undefined;
86
+ }
87
+ }
88
+
89
+ return {
90
+ type: 'NobleBleIpcError',
91
+ success: false,
92
+ error: {
93
+ name,
94
+ message,
95
+ errorCode,
96
+ ...(params !== undefined ? { params } : {}),
97
+ },
98
+ };
99
+ }
100
+
42
101
  // Bluetooth state management
43
102
  const bluetoothState: {
44
103
  available: boolean;
@@ -1252,9 +1311,14 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
1252
1311
  }
1253
1312
 
1254
1313
  // Step 3: Re-establish connection
1255
- await runBleCallbackOperation(callback => peripheral.connect(callback), {
1256
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1257
- timeoutBehavior: 'reject',
1314
+ await new Promise<void>((resolve, reject) => {
1315
+ peripheral.connect(error => {
1316
+ if (error) {
1317
+ reject(createNobleBleConnectionError(error));
1318
+ return;
1319
+ }
1320
+ resolve();
1321
+ });
1258
1322
  });
1259
1323
  logger?.info('[NobleBLE] Force reconnect successful');
1260
1324
  connectedDevices.set(deviceId, peripheral);
@@ -1296,12 +1360,7 @@ async function freshScanAndDiscover(
1296
1360
  await new Promise<void>((resolve, reject) => {
1297
1361
  freshPeripheral.connect((error: Error | undefined) => {
1298
1362
  if (error) {
1299
- reject(
1300
- ERRORS.TypedError(
1301
- HardwareErrorCode.BleConnectedError,
1302
- `Fresh peripheral connection failed: ${error.message}`
1303
- )
1304
- );
1363
+ reject(createNobleBleConnectionError(error, 'Fresh peripheral connection failed: '));
1305
1364
  } else {
1306
1365
  connectedDevices.set(deviceId, freshPeripheral);
1307
1366
  resolve();
@@ -1414,6 +1473,9 @@ async function setupConnectionAndDiscoverServices(
1414
1473
  try {
1415
1474
  await forceReconnectPeripheral(peripheral, deviceId);
1416
1475
  } catch (resetError) {
1476
+ if (isBleStaleBondHardwareError(resetError)) {
1477
+ throw resetError;
1478
+ }
1417
1479
  // A failed reset must not abort the attempt: discovery on the existing
1418
1480
  // link, then the fresh-scan fallback, still have a chance to recover.
1419
1481
  logger?.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
@@ -1666,33 +1728,12 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
1666
1728
  }
1667
1729
 
1668
1730
  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
-
1675
1731
  // TypeScript type assertion - peripheral is guaranteed to be defined at this point
1676
1732
  const connectedPeripheral = peripheral as Peripheral;
1677
1733
  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
-
1693
1734
  if (error) {
1694
1735
  logger?.error('[NobleBLE] Connection failed:', error);
1695
- reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
1736
+ reject(createNobleBleConnectionError(error));
1696
1737
  return;
1697
1738
  }
1698
1739
 
@@ -1875,7 +1916,7 @@ async function subscribeNotifications(
1875
1916
  timeoutBehavior: 'resolve',
1876
1917
  });
1877
1918
  await runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
1878
- timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
1919
+ timeoutMs: NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS,
1879
1920
  timeoutBehavior: 'reject',
1880
1921
  });
1881
1922
 
@@ -1918,7 +1959,13 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
1918
1959
  // Electron throws on duplicate channels and setup re-runs on soft restart.
1919
1960
  const handle: IpcMain['handle'] = (channel, listener) => {
1920
1961
  ipcMain.removeHandler(channel);
1921
- ipcMain.handle(channel, listener);
1962
+ ipcMain.handle(channel, async (...args) => {
1963
+ try {
1964
+ return await Promise.resolve(listener(...args));
1965
+ } catch (error) {
1966
+ return createNobleBleIpcErrorResponse(error);
1967
+ }
1968
+ });
1922
1969
  };
1923
1970
 
1924
1971
  safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
@@ -1,2 +1,2 @@
1
1
  export const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5_000;
2
- export const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10_000;
2
+ export const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10_000;
@@ -10,6 +10,19 @@ export interface NobleBleWriteOptions {
10
10
  pacingDelayMs?: number;
11
11
  }
12
12
 
13
+ export type NobleBleIpcErrorPayload = {
14
+ name: string;
15
+ message: string;
16
+ errorCode: number;
17
+ params?: unknown;
18
+ };
19
+
20
+ export type NobleBleIpcErrorResponse = {
21
+ type: 'NobleBleIpcError';
22
+ success: false;
23
+ error: NobleBleIpcErrorPayload;
24
+ };
25
+
13
26
  export interface NobleBleAPI {
14
27
  enumerate: () => Promise<{ id: string; name: string }[]>;
15
28
  getDevice: (uuid: string) => Promise<{ id: string; name: string; mtu?: number } | null>;