@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.
- package/dist/{index-00297050.js → index-d51c7da6.js} +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-95c5cf0d.js → noble-ble-handler-0b455c4f.js} +60 -25
- package/dist/noble-ble-handler.d.ts +8 -1
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/noble-ble-timeouts.d.ts +1 -1
- package/dist/noble-ble-timeouts.d.ts.map +1 -1
- package/dist/types/desktop-api.d.ts +11 -0
- package/dist/types/desktop-api.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/noble-ble-handler.test.ts +120 -70
- package/src/index.ts +7 -1
- package/src/noble-ble-handler.ts +82 -35
- package/src/noble-ble-timeouts.ts +1 -1
- package/src/types/desktop-api.ts +13 -0
|
@@ -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-
|
|
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 };
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
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
|
|
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
|
|
928
|
-
|
|
929
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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,
|
|
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":"
|
|
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 +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
|
|
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.
|
|
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.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
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": "
|
|
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
|
-
|
|
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('
|
|
29
|
+
test('bounds targeted scans and subscription callbacks independently', () => {
|
|
30
30
|
expect(NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS).toBe(5_000);
|
|
31
|
-
expect(
|
|
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).
|
|
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('
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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 {
|
|
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');
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1256
|
-
|
|
1257
|
-
|
|
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(
|
|
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:
|
|
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,
|
|
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
|
|
2
|
+
export const NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS = 10_000;
|
package/src/types/desktop-api.ts
CHANGED
|
@@ -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>;
|