@onekeyfe/hd-transport-electron 1.2.8-alpha.9 → 1.2.8
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-21e34fdb.js → index-e2e3d2ba.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-c4530a1b.js → noble-ble-handler-d0991fa0.js} +39 -22
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/noble-ble-handler.test.ts +108 -86
- package/src/noble-ble-handler.ts +44 -21
|
@@ -55,13 +55,13 @@ function invokeNobleBleIpc(request) {
|
|
|
55
55
|
|
|
56
56
|
function initNobleBleSupport(webContents) {
|
|
57
57
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
-
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-
|
|
58
|
+
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-d0991fa0.js'); });
|
|
59
59
|
setupNobleBleHandlers(webContents);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
function disposeNobleBleSupport(releaseNoble) {
|
|
63
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
-
const handler = yield Promise.resolve().then(function () { return require('./noble-ble-handler-
|
|
64
|
+
const handler = yield Promise.resolve().then(function () { return require('./noble-ble-handler-d0991fa0.js'); });
|
|
65
65
|
yield handler.disposeNobleBleSupport(releaseNoble);
|
|
66
66
|
});
|
|
67
67
|
}
|
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-e2e3d2ba.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
|
|
@@ -123,6 +123,13 @@ function assertBleActive() {
|
|
|
123
123
|
}
|
|
124
124
|
function createNobleBleConnectionError(error, messagePrefix = '') {
|
|
125
125
|
const errorMessage = error.message;
|
|
126
|
+
const isInvalidMacOsBond = error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain';
|
|
127
|
+
if (isInvalidMacOsBond) {
|
|
128
|
+
const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
|
|
129
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, `${hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`, {
|
|
130
|
+
nativeErrorMessage,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
126
133
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
|
|
127
134
|
}
|
|
128
135
|
function createNobleBleIpcErrorResponse(error) {
|
|
@@ -376,18 +383,8 @@ function updateBluetoothState(state) {
|
|
|
376
383
|
function initializeNoble() {
|
|
377
384
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
378
385
|
assertBleActive();
|
|
379
|
-
if (noble)
|
|
380
|
-
if (noble.state === 'poweredOff') {
|
|
381
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
|
|
382
|
-
}
|
|
383
|
-
if (noble.state === 'unsupported') {
|
|
384
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
|
|
385
|
-
}
|
|
386
|
-
if (noble.state === 'unauthorized') {
|
|
387
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError);
|
|
388
|
-
}
|
|
386
|
+
if (noble)
|
|
389
387
|
return;
|
|
390
|
-
}
|
|
391
388
|
try {
|
|
392
389
|
noble = require('@stoprocent/noble');
|
|
393
390
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
|
|
@@ -428,7 +425,7 @@ function initializeNoble() {
|
|
|
428
425
|
}
|
|
429
426
|
else if (state === 'unauthorized') {
|
|
430
427
|
cleanup();
|
|
431
|
-
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
428
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
|
|
432
429
|
}
|
|
433
430
|
};
|
|
434
431
|
const cancel = () => {
|
|
@@ -437,7 +434,6 @@ function initializeNoble() {
|
|
|
437
434
|
};
|
|
438
435
|
pendingCancellations.add(cancel);
|
|
439
436
|
noble.on('stateChange', onStateChange);
|
|
440
|
-
onStateChange(noble.state);
|
|
441
437
|
});
|
|
442
438
|
assertBleActive();
|
|
443
439
|
if (!persistentDiscoverListener) {
|
|
@@ -450,7 +446,7 @@ function initializeNoble() {
|
|
|
450
446
|
}
|
|
451
447
|
catch (error) {
|
|
452
448
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to initialize Noble:', error);
|
|
453
|
-
bluetoothState.unsupported =
|
|
449
|
+
bluetoothState.unsupported = true;
|
|
454
450
|
bluetoothState.initialized = true;
|
|
455
451
|
throw error;
|
|
456
452
|
}
|
|
@@ -879,7 +875,9 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
879
875
|
}
|
|
880
876
|
function enumerateDevices(isWindowDestroyed) {
|
|
881
877
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
882
|
-
|
|
878
|
+
if (!noble) {
|
|
879
|
+
yield initializeNoble();
|
|
880
|
+
}
|
|
883
881
|
if (!noble) {
|
|
884
882
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
885
883
|
}
|
|
@@ -1178,7 +1176,8 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
|
1178
1176
|
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
1179
1177
|
}
|
|
1180
1178
|
catch (resetError) {
|
|
1181
|
-
if ((resetError
|
|
1179
|
+
if (hdShared.isBleStaleBondHardwareError(resetError) ||
|
|
1180
|
+
(resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
|
|
1182
1181
|
throw resetError;
|
|
1183
1182
|
}
|
|
1184
1183
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
|
|
@@ -1259,6 +1258,10 @@ function tryDirectConnectById(deviceId) {
|
|
|
1259
1258
|
deviceId,
|
|
1260
1259
|
error: String(error),
|
|
1261
1260
|
});
|
|
1261
|
+
const nativeError = error;
|
|
1262
|
+
if (nativeError.nativeErrorCode === 14 && nativeError.nativeErrorDomain === 'CBErrorDomain') {
|
|
1263
|
+
throw createNobleBleConnectionError(nativeError);
|
|
1264
|
+
}
|
|
1262
1265
|
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1263
1266
|
return undefined;
|
|
1264
1267
|
}
|
|
@@ -1272,7 +1275,6 @@ function tryDirectConnectById(deviceId) {
|
|
|
1272
1275
|
function connectDevice(deviceId, webContents) {
|
|
1273
1276
|
var _a, _b, _c;
|
|
1274
1277
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1275
|
-
yield initializeNoble();
|
|
1276
1278
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
1277
1279
|
deviceId,
|
|
1278
1280
|
hasDiscovered: discoveredDevices.has(deviceId),
|
|
@@ -1286,6 +1288,9 @@ function connectDevice(deviceId, webContents) {
|
|
|
1286
1288
|
peripheral = undefined;
|
|
1287
1289
|
}
|
|
1288
1290
|
if (!peripheral) {
|
|
1291
|
+
if (!noble) {
|
|
1292
|
+
yield initializeNoble();
|
|
1293
|
+
}
|
|
1289
1294
|
if (!noble) {
|
|
1290
1295
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1291
1296
|
}
|
|
@@ -1308,17 +1313,29 @@ function connectDevice(deviceId, webContents) {
|
|
|
1308
1313
|
return undefined;
|
|
1309
1314
|
}
|
|
1310
1315
|
});
|
|
1316
|
+
let staleBondError;
|
|
1311
1317
|
const connectById = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1318
|
+
try {
|
|
1319
|
+
const found = yield tryDirectConnectById(deviceId);
|
|
1320
|
+
if (found) {
|
|
1321
|
+
discoveredDevices.set(deviceId, found);
|
|
1322
|
+
}
|
|
1323
|
+
return found;
|
|
1324
|
+
}
|
|
1325
|
+
catch (error) {
|
|
1326
|
+
if (error.errorCode !== hdShared.HardwareErrorCode.BleBondInvalid) {
|
|
1327
|
+
throw error;
|
|
1328
|
+
}
|
|
1329
|
+
staleBondError = error;
|
|
1330
|
+
return undefined;
|
|
1315
1331
|
}
|
|
1316
|
-
return found;
|
|
1317
1332
|
});
|
|
1318
1333
|
peripheral = byIdFirst ? yield connectById() : yield scanForPeripheral();
|
|
1319
1334
|
if (!peripheral) {
|
|
1320
1335
|
peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
|
|
1321
1336
|
}
|
|
1337
|
+
if (!peripheral && staleBondError)
|
|
1338
|
+
throw staleBondError;
|
|
1322
1339
|
}
|
|
1323
1340
|
assertBleActive();
|
|
1324
1341
|
if (!peripheral) {
|
|
@@ -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;AAiC1F,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+KD,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AA2zDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAkOpE;AAOD,wBAAgB,sBAAsB,CACpC,YAAY,GAAE,CAAC,QAAQ,EAAE;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,KAAK,IAAkC,GAC/E,OAAO,CAAC,IAAI,CAAC,CAiEf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-electron",
|
|
3
|
-
"version": "1.2.8
|
|
3
|
+
"version": "1.2.8",
|
|
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.8
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.8
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.8
|
|
28
|
+
"@onekeyfe/hd-core": "1.2.8",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.8",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.8",
|
|
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": "9f016278a7f395bf66b8502815dc63191e162fd3"
|
|
40
40
|
}
|
|
@@ -31,17 +31,22 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
31
31
|
expect(NOBLE_BLE_SUBSCRIBE_TIMEOUT_MS).toBe(10_000);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test('maps
|
|
34
|
+
test('maps structured macOS stale pairing failures without parsing localized text', async () => {
|
|
35
35
|
const { createNobleBleConnectionError } = await import('../noble-ble-handler');
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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');
|
|
45
50
|
expect(createNobleBleConnectionError(new Error('Encryption is insufficient'))).toMatchObject({
|
|
46
51
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
47
52
|
});
|
|
@@ -76,16 +81,18 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
76
81
|
expect(
|
|
77
82
|
createNobleBleIpcErrorResponse({
|
|
78
83
|
name: 'HardwareError',
|
|
79
|
-
message: 'Bluetooth
|
|
80
|
-
errorCode: HardwareErrorCode.
|
|
84
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
85
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
86
|
+
params: { nativeErrorMessage: 'native message' },
|
|
81
87
|
})
|
|
82
88
|
).toEqual({
|
|
83
89
|
type: 'NobleBleIpcError',
|
|
84
90
|
success: false,
|
|
85
91
|
error: {
|
|
86
92
|
name: 'HardwareError',
|
|
87
|
-
message: 'Bluetooth
|
|
88
|
-
errorCode: HardwareErrorCode.
|
|
93
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
94
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
95
|
+
params: { nativeErrorMessage: 'native message' },
|
|
89
96
|
},
|
|
90
97
|
});
|
|
91
98
|
|
|
@@ -128,14 +135,14 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
128
135
|
success: false as const,
|
|
129
136
|
error: {
|
|
130
137
|
name: 'HardwareError',
|
|
131
|
-
message: 'Bluetooth
|
|
132
|
-
errorCode: HardwareErrorCode.
|
|
138
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
139
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
133
140
|
},
|
|
134
141
|
})
|
|
135
142
|
)
|
|
136
143
|
).rejects.toMatchObject({
|
|
137
144
|
name: 'HardwareError',
|
|
138
|
-
errorCode: HardwareErrorCode.
|
|
145
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
139
146
|
});
|
|
140
147
|
|
|
141
148
|
await expect(invokeNobleBleIpc(Promise.resolve('connected'))).resolves.toBe('connected');
|
|
@@ -292,13 +299,9 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
292
299
|
}
|
|
293
300
|
);
|
|
294
301
|
|
|
295
|
-
test.each([
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
['Pro2 A1B2', 'native error 14'],
|
|
299
|
-
])(
|
|
300
|
-
'refreshes a retained disconnected %s peripheral before cold connecting (direct: %s)',
|
|
301
|
-
async (name, directConnectResult) => {
|
|
302
|
+
test.each(['Pro A1B2', 'Pro2 A1B2'])(
|
|
303
|
+
'refreshes a retained disconnected %s peripheral before cold connecting',
|
|
304
|
+
async name => {
|
|
302
305
|
const handlers = new Map<string, IpcHandler>();
|
|
303
306
|
const stalePeripheral = Object.assign(new EventEmitter(), createPeripheral('device', name), {
|
|
304
307
|
connect: jest.fn(),
|
|
@@ -315,16 +318,7 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
315
318
|
noble.emit('discover', freshPeripheral);
|
|
316
319
|
}),
|
|
317
320
|
stopScanning: jest.fn(callback => callback?.()),
|
|
318
|
-
connectAsync: jest.fn(() =>
|
|
319
|
-
directConnectResult === 'native error 14'
|
|
320
|
-
? Promise.reject(
|
|
321
|
-
Object.assign(new Error('native direct connection failure'), {
|
|
322
|
-
nativeErrorDomain: 'CBErrorDomain',
|
|
323
|
-
nativeErrorCode: 14,
|
|
324
|
-
})
|
|
325
|
-
)
|
|
326
|
-
: Promise.resolve(undefined)
|
|
327
|
-
),
|
|
321
|
+
connectAsync: jest.fn(() => Promise.resolve(undefined)),
|
|
328
322
|
});
|
|
329
323
|
jest.doMock('@stoprocent/noble', () => noble);
|
|
330
324
|
jest.doMock('electron', () => ({
|
|
@@ -358,6 +352,87 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
358
352
|
}
|
|
359
353
|
);
|
|
360
354
|
|
|
355
|
+
test.each([
|
|
356
|
+
{ rediscovered: false, expectedCode: HardwareErrorCode.BleBondInvalid },
|
|
357
|
+
{ rediscovered: true, expectedCode: HardwareErrorCode.BleConnectedError },
|
|
358
|
+
])(
|
|
359
|
+
'reports a macOS stale bond only when fallback scan misses (rediscovered=$rediscovered)',
|
|
360
|
+
async ({ rediscovered, expectedCode }) => {
|
|
361
|
+
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
362
|
+
const handlers = new Map<string, IpcHandler>();
|
|
363
|
+
const stalePeripheral = createPeripheral('device', 'Pro2 A1B2');
|
|
364
|
+
const freshPeripheral = Object.assign(
|
|
365
|
+
new EventEmitter(),
|
|
366
|
+
createPeripheral('device', 'Pro2 A1B2'),
|
|
367
|
+
{
|
|
368
|
+
connect: jest.fn((callback: (error?: Error) => void) => {
|
|
369
|
+
callback(new Error('fresh connection failed'));
|
|
370
|
+
}),
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
const staleBondError = Object.assign(new Error('Peer removed pairing information'), {
|
|
374
|
+
nativeErrorCode: 14,
|
|
375
|
+
nativeErrorDomain: 'CBErrorDomain',
|
|
376
|
+
});
|
|
377
|
+
let scanStarted: (() => void) | undefined;
|
|
378
|
+
const scanning = new Promise<void>(resolve => {
|
|
379
|
+
scanStarted = resolve;
|
|
380
|
+
});
|
|
381
|
+
const noble = Object.assign(new EventEmitter(), {
|
|
382
|
+
state: 'poweredOn',
|
|
383
|
+
startScanning: jest.fn((_services, _duplicates, callback) => {
|
|
384
|
+
callback?.();
|
|
385
|
+
if (rediscovered) noble.emit('discover', freshPeripheral);
|
|
386
|
+
scanStarted?.();
|
|
387
|
+
}),
|
|
388
|
+
stopScanning: jest.fn(callback => callback?.()),
|
|
389
|
+
connectAsync: jest.fn(() => Promise.reject(staleBondError)),
|
|
390
|
+
});
|
|
391
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
392
|
+
jest.doMock('electron', () => ({
|
|
393
|
+
ipcMain: {
|
|
394
|
+
handle: (channel: string, handler: IpcHandler) => handlers.set(channel, handler),
|
|
395
|
+
removeHandler: (channel: string) => handlers.delete(channel),
|
|
396
|
+
},
|
|
397
|
+
}));
|
|
398
|
+
jest.doMock('electron-log', () => ({
|
|
399
|
+
info: jest.fn(),
|
|
400
|
+
debug: jest.fn(),
|
|
401
|
+
error: jest.fn(),
|
|
402
|
+
}));
|
|
403
|
+
const { setupNobleBleHandlers } = await import('../noble-ble-handler');
|
|
404
|
+
setupNobleBleHandlers({ on: jest.fn(), send: jest.fn() } as unknown as WebContents);
|
|
405
|
+
const availability = handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK);
|
|
406
|
+
const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
|
|
407
|
+
if (!availability || !connect) throw new Error('Noble handlers were not registered');
|
|
408
|
+
await availability();
|
|
409
|
+
noble.emit('discover', stalePeripheral);
|
|
410
|
+
|
|
411
|
+
const connecting = Promise.resolve(connect(undefined, stalePeripheral.id));
|
|
412
|
+
await scanning;
|
|
413
|
+
jest.advanceTimersByTime(1500);
|
|
414
|
+
await expect(connecting).resolves.toMatchObject({
|
|
415
|
+
success: false,
|
|
416
|
+
error: { errorCode: expectedCode },
|
|
417
|
+
});
|
|
418
|
+
if (!rediscovered) {
|
|
419
|
+
const secondScan = new Promise<void>(resolve => {
|
|
420
|
+
scanStarted = resolve;
|
|
421
|
+
});
|
|
422
|
+
const retry = Promise.resolve(connect(undefined, stalePeripheral.id));
|
|
423
|
+
await secondScan;
|
|
424
|
+
jest.advanceTimersByTime(1500);
|
|
425
|
+
await expect(retry).resolves.toMatchObject({
|
|
426
|
+
success: false,
|
|
427
|
+
error: { errorCode: HardwareErrorCode.BleBondInvalid },
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
expect(noble.connectAsync).toHaveBeenCalledTimes(rediscovered ? 1 : 2);
|
|
431
|
+
expect(noble.startScanning).toHaveBeenCalledTimes(rediscovered ? 1 : 2);
|
|
432
|
+
expect(freshPeripheral.connect).toHaveBeenCalledTimes(rediscovered ? 1 : 0);
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
|
|
361
436
|
test('waits for a targeted scan to stop before connecting', async () => {
|
|
362
437
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
363
438
|
|
|
@@ -931,59 +1006,6 @@ describe('Noble BLE process shutdown', () => {
|
|
|
931
1006
|
expect(handlers.size).toBe(0);
|
|
932
1007
|
});
|
|
933
1008
|
|
|
934
|
-
test.each([
|
|
935
|
-
['poweredOff', HardwareErrorCode.BlePoweredOff],
|
|
936
|
-
['unauthorized', HardwareErrorCode.BleLocationError],
|
|
937
|
-
['unsupported', HardwareErrorCode.BleUnsupported],
|
|
938
|
-
])(
|
|
939
|
-
'preserves adapter state %s across repeated scans and connections',
|
|
940
|
-
async (state, errorCode) => {
|
|
941
|
-
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
942
|
-
const { sdk, native, handlers } = await setup(state);
|
|
943
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
944
|
-
for (const channel of [
|
|
945
|
-
EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE,
|
|
946
|
-
EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT,
|
|
947
|
-
]) {
|
|
948
|
-
await expect(handlers.get(channel)?.({}, 'device')).resolves.toMatchObject({
|
|
949
|
-
success: false,
|
|
950
|
-
error: { errorCode },
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
expect(native.startScanning).not.toHaveBeenCalled();
|
|
955
|
-
expect(native.listenerCount('stateChange')).toBe(1);
|
|
956
|
-
expect(jest.getTimerCount()).toBe(0);
|
|
957
|
-
await sdk.disposeNobleBleSupport();
|
|
958
|
-
}
|
|
959
|
-
);
|
|
960
|
-
|
|
961
|
-
test.each([
|
|
962
|
-
['poweredOff', HardwareErrorCode.BlePoweredOff],
|
|
963
|
-
['unauthorized', HardwareErrorCode.BleLocationError],
|
|
964
|
-
['unsupported', HardwareErrorCode.BleUnsupported],
|
|
965
|
-
])('checks adapter state %s again after initialization', async (state, errorCode) => {
|
|
966
|
-
const { sdk, native, handlers } = await setup();
|
|
967
|
-
const checkAvailability = handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK);
|
|
968
|
-
await expect(checkAvailability?.({})).resolves.toMatchObject({ available: true });
|
|
969
|
-
native.state = state;
|
|
970
|
-
native.emit('stateChange', state);
|
|
971
|
-
for (const channel of [
|
|
972
|
-
EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE,
|
|
973
|
-
EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT,
|
|
974
|
-
]) {
|
|
975
|
-
await expect(handlers.get(channel)?.({}, 'device')).resolves.toMatchObject({
|
|
976
|
-
success: false,
|
|
977
|
-
error: { errorCode },
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
expect(native.startScanning).not.toHaveBeenCalled();
|
|
981
|
-
native.state = 'poweredOn';
|
|
982
|
-
native.emit('stateChange', native.state);
|
|
983
|
-
await expect(checkAvailability?.({})).resolves.toMatchObject({ available: true });
|
|
984
|
-
await sdk.disposeNobleBleSupport();
|
|
985
|
-
});
|
|
986
|
-
|
|
987
1009
|
test('cancels an active scan, ignores its late callback and releases native once', async () => {
|
|
988
1010
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
989
1011
|
const { sdk, native, handlers } = await setup();
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
ONEKEY_WRITE_CHARACTERISTIC_UUID,
|
|
17
17
|
createKnownBleUuidAliases,
|
|
18
18
|
hasOnekeyCommunicationService,
|
|
19
|
+
isBleStaleBondHardwareError,
|
|
19
20
|
isOnekeyBluetoothDevice,
|
|
20
21
|
isPro2FamilyBleName,
|
|
21
22
|
matchesKnownBleUuid,
|
|
@@ -73,6 +74,19 @@ type NobleBleNativeError = Error & {
|
|
|
73
74
|
|
|
74
75
|
export function createNobleBleConnectionError(error: NobleBleNativeError, messagePrefix = '') {
|
|
75
76
|
const errorMessage = error.message;
|
|
77
|
+
const isInvalidMacOsBond =
|
|
78
|
+
error.nativeErrorCode === 14 && error.nativeErrorDomain === 'CBErrorDomain';
|
|
79
|
+
if (isInvalidMacOsBond) {
|
|
80
|
+
const nativeErrorMessage = `${messagePrefix}${errorMessage}`;
|
|
81
|
+
return ERRORS.TypedError(
|
|
82
|
+
HardwareErrorCode.BleBondInvalid,
|
|
83
|
+
`${HardwareErrorCodeMessage[HardwareErrorCode.BleBondInvalid]} (${nativeErrorMessage})`,
|
|
84
|
+
{
|
|
85
|
+
nativeErrorMessage,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
76
90
|
return ERRORS.TypedError(HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
|
|
77
91
|
}
|
|
78
92
|
|
|
@@ -429,18 +443,7 @@ function updateBluetoothState(state: string): void {
|
|
|
429
443
|
// Initialize Noble
|
|
430
444
|
async function initializeNoble(): Promise<void> {
|
|
431
445
|
assertBleActive();
|
|
432
|
-
if (noble)
|
|
433
|
-
if (noble.state === 'poweredOff') {
|
|
434
|
-
throw ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
|
|
435
|
-
}
|
|
436
|
-
if (noble.state === 'unsupported') {
|
|
437
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
|
|
438
|
-
}
|
|
439
|
-
if (noble.state === 'unauthorized') {
|
|
440
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleLocationError);
|
|
441
|
-
}
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
446
|
+
if (noble) return;
|
|
444
447
|
|
|
445
448
|
try {
|
|
446
449
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
@@ -493,7 +496,7 @@ async function initializeNoble(): Promise<void> {
|
|
|
493
496
|
reject(ERRORS.TypedError(HardwareErrorCode.BlePoweredOff));
|
|
494
497
|
} else if (state === 'unauthorized') {
|
|
495
498
|
cleanup();
|
|
496
|
-
reject(ERRORS.TypedError(HardwareErrorCode.
|
|
499
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BlePermissionError));
|
|
497
500
|
}
|
|
498
501
|
};
|
|
499
502
|
|
|
@@ -505,7 +508,6 @@ async function initializeNoble(): Promise<void> {
|
|
|
505
508
|
};
|
|
506
509
|
pendingCancellations.add(cancel);
|
|
507
510
|
noble.on('stateChange', onStateChange);
|
|
508
|
-
onStateChange(noble.state);
|
|
509
511
|
});
|
|
510
512
|
|
|
511
513
|
assertBleActive();
|
|
@@ -520,7 +522,7 @@ async function initializeNoble(): Promise<void> {
|
|
|
520
522
|
logger?.info('[NobleBLE] Noble initialized successfully');
|
|
521
523
|
} catch (error) {
|
|
522
524
|
logger?.error('[NobleBLE] Failed to initialize Noble:', error);
|
|
523
|
-
bluetoothState.unsupported =
|
|
525
|
+
bluetoothState.unsupported = true;
|
|
524
526
|
bluetoothState.initialized = true;
|
|
525
527
|
throw error;
|
|
526
528
|
}
|
|
@@ -1149,7 +1151,9 @@ async function performTargetedScan(
|
|
|
1149
1151
|
|
|
1150
1152
|
// Enumerate devices
|
|
1151
1153
|
async function enumerateDevices(isWindowDestroyed: () => boolean): Promise<DeviceInfo[]> {
|
|
1152
|
-
|
|
1154
|
+
if (!noble) {
|
|
1155
|
+
await initializeNoble();
|
|
1156
|
+
}
|
|
1153
1157
|
|
|
1154
1158
|
if (!noble) {
|
|
1155
1159
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
@@ -1630,6 +1634,7 @@ async function setupConnectionAndDiscoverServices(
|
|
|
1630
1634
|
await forceReconnectPeripheral(peripheral, deviceId);
|
|
1631
1635
|
} catch (resetError) {
|
|
1632
1636
|
if (
|
|
1637
|
+
isBleStaleBondHardwareError(resetError) ||
|
|
1633
1638
|
(resetError as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleDeviceDisconnected
|
|
1634
1639
|
) {
|
|
1635
1640
|
throw resetError;
|
|
@@ -1740,6 +1745,10 @@ async function tryDirectConnectById(deviceId: string): Promise<Peripheral | unde
|
|
|
1740
1745
|
deviceId,
|
|
1741
1746
|
error: String(error),
|
|
1742
1747
|
});
|
|
1748
|
+
const nativeError = error as NobleBleNativeError;
|
|
1749
|
+
if (nativeError.nativeErrorCode === 14 && nativeError.nativeErrorDomain === 'CBErrorDomain') {
|
|
1750
|
+
throw createNobleBleConnectionError(nativeError);
|
|
1751
|
+
}
|
|
1743
1752
|
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1744
1753
|
return undefined;
|
|
1745
1754
|
} finally {
|
|
@@ -1750,7 +1759,6 @@ async function tryDirectConnectById(deviceId: string): Promise<Peripheral | unde
|
|
|
1750
1759
|
|
|
1751
1760
|
// Connect to device - supports both discovered and direct connection modes
|
|
1752
1761
|
async function connectDevice(deviceId: string, webContents: WebContents): Promise<void> {
|
|
1753
|
-
await initializeNoble();
|
|
1754
1762
|
logger?.info('[NobleBLE] Connect device request:', {
|
|
1755
1763
|
deviceId,
|
|
1756
1764
|
hasDiscovered: discoveredDevices.has(deviceId),
|
|
@@ -1768,6 +1776,11 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1768
1776
|
}
|
|
1769
1777
|
|
|
1770
1778
|
if (!peripheral) {
|
|
1779
|
+
// Initialize Noble if not already done
|
|
1780
|
+
if (!noble) {
|
|
1781
|
+
await initializeNoble();
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1771
1784
|
if (!noble) {
|
|
1772
1785
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1773
1786
|
}
|
|
@@ -1799,12 +1812,21 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1799
1812
|
}
|
|
1800
1813
|
};
|
|
1801
1814
|
|
|
1815
|
+
let staleBondError: Error | undefined;
|
|
1802
1816
|
const connectById = async () => {
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1817
|
+
try {
|
|
1818
|
+
const found = await tryDirectConnectById(deviceId);
|
|
1819
|
+
if (found) {
|
|
1820
|
+
discoveredDevices.set(deviceId, found);
|
|
1821
|
+
}
|
|
1822
|
+
return found;
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
if ((error as { errorCode?: number }).errorCode !== HardwareErrorCode.BleBondInvalid) {
|
|
1825
|
+
throw error;
|
|
1826
|
+
}
|
|
1827
|
+
staleBondError = error as Error;
|
|
1828
|
+
return undefined;
|
|
1806
1829
|
}
|
|
1807
|
-
return found;
|
|
1808
1830
|
};
|
|
1809
1831
|
|
|
1810
1832
|
peripheral = byIdFirst ? await connectById() : await scanForPeripheral();
|
|
@@ -1813,6 +1835,7 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1813
1835
|
// silent), or not reachable by id. Try the other one before giving up.
|
|
1814
1836
|
peripheral = byIdFirst ? await scanForPeripheral() : await connectById();
|
|
1815
1837
|
}
|
|
1838
|
+
if (!peripheral && staleBondError) throw staleBondError;
|
|
1816
1839
|
}
|
|
1817
1840
|
|
|
1818
1841
|
assertBleActive();
|