@onekeyfe/hd-transport-electron 1.2.8-alpha.1 → 1.2.8-alpha.100
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-e2e3d2ba.js → index-21e34fdb.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-d0991fa0.js → noble-ble-handler-c4530a1b.js} +22 -39
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/noble-ble-handler.test.ts +86 -108
- package/src/noble-ble-handler.ts +21 -44
|
@@ -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-c4530a1b.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-c4530a1b.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-21e34fdb.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
|
|
@@ -123,13 +123,6 @@ 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
|
-
}
|
|
133
126
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
|
|
134
127
|
}
|
|
135
128
|
function createNobleBleIpcErrorResponse(error) {
|
|
@@ -383,8 +376,18 @@ function updateBluetoothState(state) {
|
|
|
383
376
|
function initializeNoble() {
|
|
384
377
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
385
378
|
assertBleActive();
|
|
386
|
-
if (noble)
|
|
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
|
+
}
|
|
387
389
|
return;
|
|
390
|
+
}
|
|
388
391
|
try {
|
|
389
392
|
noble = require('@stoprocent/noble');
|
|
390
393
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
|
|
@@ -425,7 +428,7 @@ function initializeNoble() {
|
|
|
425
428
|
}
|
|
426
429
|
else if (state === 'unauthorized') {
|
|
427
430
|
cleanup();
|
|
428
|
-
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
431
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
|
|
429
432
|
}
|
|
430
433
|
};
|
|
431
434
|
const cancel = () => {
|
|
@@ -434,6 +437,7 @@ function initializeNoble() {
|
|
|
434
437
|
};
|
|
435
438
|
pendingCancellations.add(cancel);
|
|
436
439
|
noble.on('stateChange', onStateChange);
|
|
440
|
+
onStateChange(noble.state);
|
|
437
441
|
});
|
|
438
442
|
assertBleActive();
|
|
439
443
|
if (!persistentDiscoverListener) {
|
|
@@ -446,7 +450,7 @@ function initializeNoble() {
|
|
|
446
450
|
}
|
|
447
451
|
catch (error) {
|
|
448
452
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to initialize Noble:', error);
|
|
449
|
-
bluetoothState.unsupported =
|
|
453
|
+
bluetoothState.unsupported = (noble === null || noble === void 0 ? void 0 : noble.state) === 'unsupported';
|
|
450
454
|
bluetoothState.initialized = true;
|
|
451
455
|
throw error;
|
|
452
456
|
}
|
|
@@ -875,9 +879,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
875
879
|
}
|
|
876
880
|
function enumerateDevices(isWindowDestroyed) {
|
|
877
881
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
878
|
-
|
|
879
|
-
yield initializeNoble();
|
|
880
|
-
}
|
|
882
|
+
yield initializeNoble();
|
|
881
883
|
if (!noble) {
|
|
882
884
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
883
885
|
}
|
|
@@ -1176,8 +1178,7 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
|
1176
1178
|
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
1177
1179
|
}
|
|
1178
1180
|
catch (resetError) {
|
|
1179
|
-
if (
|
|
1180
|
-
(resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
|
|
1181
|
+
if ((resetError === null || resetError === void 0 ? void 0 : resetError.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
|
|
1181
1182
|
throw resetError;
|
|
1182
1183
|
}
|
|
1183
1184
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection reset before discovery failed, continuing', resetError);
|
|
@@ -1258,10 +1259,6 @@ function tryDirectConnectById(deviceId) {
|
|
|
1258
1259
|
deviceId,
|
|
1259
1260
|
error: String(error),
|
|
1260
1261
|
});
|
|
1261
|
-
const nativeError = error;
|
|
1262
|
-
if (nativeError.nativeErrorCode === 14 && nativeError.nativeErrorDomain === 'CBErrorDomain') {
|
|
1263
|
-
throw createNobleBleConnectionError(nativeError);
|
|
1264
|
-
}
|
|
1265
1262
|
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1266
1263
|
return undefined;
|
|
1267
1264
|
}
|
|
@@ -1275,6 +1272,7 @@ function tryDirectConnectById(deviceId) {
|
|
|
1275
1272
|
function connectDevice(deviceId, webContents) {
|
|
1276
1273
|
var _a, _b, _c;
|
|
1277
1274
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1275
|
+
yield initializeNoble();
|
|
1278
1276
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
1279
1277
|
deviceId,
|
|
1280
1278
|
hasDiscovered: discoveredDevices.has(deviceId),
|
|
@@ -1288,9 +1286,6 @@ function connectDevice(deviceId, webContents) {
|
|
|
1288
1286
|
peripheral = undefined;
|
|
1289
1287
|
}
|
|
1290
1288
|
if (!peripheral) {
|
|
1291
|
-
if (!noble) {
|
|
1292
|
-
yield initializeNoble();
|
|
1293
|
-
}
|
|
1294
1289
|
if (!noble) {
|
|
1295
1290
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1296
1291
|
}
|
|
@@ -1313,29 +1308,17 @@ function connectDevice(deviceId, webContents) {
|
|
|
1313
1308
|
return undefined;
|
|
1314
1309
|
}
|
|
1315
1310
|
});
|
|
1316
|
-
let staleBondError;
|
|
1317
1311
|
const connectById = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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;
|
|
1312
|
+
const found = yield tryDirectConnectById(deviceId);
|
|
1313
|
+
if (found) {
|
|
1314
|
+
discoveredDevices.set(deviceId, found);
|
|
1331
1315
|
}
|
|
1316
|
+
return found;
|
|
1332
1317
|
});
|
|
1333
1318
|
peripheral = byIdFirst ? yield connectById() : yield scanForPeripheral();
|
|
1334
1319
|
if (!peripheral) {
|
|
1335
1320
|
peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
|
|
1336
1321
|
}
|
|
1337
|
-
if (!peripheral && staleBondError)
|
|
1338
|
-
throw staleBondError;
|
|
1339
1322
|
}
|
|
1340
1323
|
assertBleActive();
|
|
1341
1324
|
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":"AAiCA,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,+CAG3F;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAgCvF;AA+KD,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AAkzDD,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-alpha.
|
|
3
|
+
"version": "1.2.8-alpha.100",
|
|
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-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.8-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.8-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.2.8-alpha.100",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.8-alpha.100",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.8-alpha.100",
|
|
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": "53ada5b79a51f0edf8a7392f70c8da063144d33c"
|
|
40
40
|
}
|
|
@@ -31,22 +31,17 @@ 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 native macOS connection failures to the generic connection error', async () => {
|
|
35
35
|
const { createNobleBleConnectionError } = await import('../noble-ble-handler');
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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');
|
|
37
|
+
expect(
|
|
38
|
+
createNobleBleConnectionError(
|
|
39
|
+
Object.assign(new Error('Peer removed pairing information on the device side'), {
|
|
40
|
+
nativeErrorCode: 14,
|
|
41
|
+
nativeErrorDomain: 'CBErrorDomain',
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
).toMatchObject({ errorCode: HardwareErrorCode.BleConnectedError });
|
|
50
45
|
expect(createNobleBleConnectionError(new Error('Encryption is insufficient'))).toMatchObject({
|
|
51
46
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
52
47
|
});
|
|
@@ -81,18 +76,16 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
81
76
|
expect(
|
|
82
77
|
createNobleBleIpcErrorResponse({
|
|
83
78
|
name: 'HardwareError',
|
|
84
|
-
message: 'Bluetooth
|
|
85
|
-
errorCode: HardwareErrorCode.
|
|
86
|
-
params: { nativeErrorMessage: 'native message' },
|
|
79
|
+
message: 'Bluetooth connection failed',
|
|
80
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
87
81
|
})
|
|
88
82
|
).toEqual({
|
|
89
83
|
type: 'NobleBleIpcError',
|
|
90
84
|
success: false,
|
|
91
85
|
error: {
|
|
92
86
|
name: 'HardwareError',
|
|
93
|
-
message: 'Bluetooth
|
|
94
|
-
errorCode: HardwareErrorCode.
|
|
95
|
-
params: { nativeErrorMessage: 'native message' },
|
|
87
|
+
message: 'Bluetooth connection failed',
|
|
88
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
96
89
|
},
|
|
97
90
|
});
|
|
98
91
|
|
|
@@ -135,14 +128,14 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
135
128
|
success: false as const,
|
|
136
129
|
error: {
|
|
137
130
|
name: 'HardwareError',
|
|
138
|
-
message: 'Bluetooth
|
|
139
|
-
errorCode: HardwareErrorCode.
|
|
131
|
+
message: 'Bluetooth connection failed',
|
|
132
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
140
133
|
},
|
|
141
134
|
})
|
|
142
135
|
)
|
|
143
136
|
).rejects.toMatchObject({
|
|
144
137
|
name: 'HardwareError',
|
|
145
|
-
errorCode: HardwareErrorCode.
|
|
138
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
146
139
|
});
|
|
147
140
|
|
|
148
141
|
await expect(invokeNobleBleIpc(Promise.resolve('connected'))).resolves.toBe('connected');
|
|
@@ -299,9 +292,13 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
299
292
|
}
|
|
300
293
|
);
|
|
301
294
|
|
|
302
|
-
test.each([
|
|
303
|
-
'
|
|
304
|
-
|
|
295
|
+
test.each([
|
|
296
|
+
['Pro A1B2', 'unavailable'],
|
|
297
|
+
['Pro2 A1B2', 'unavailable'],
|
|
298
|
+
['Pro2 A1B2', 'native error 14'],
|
|
299
|
+
])(
|
|
300
|
+
'refreshes a retained disconnected %s peripheral before cold connecting (direct: %s)',
|
|
301
|
+
async (name, directConnectResult) => {
|
|
305
302
|
const handlers = new Map<string, IpcHandler>();
|
|
306
303
|
const stalePeripheral = Object.assign(new EventEmitter(), createPeripheral('device', name), {
|
|
307
304
|
connect: jest.fn(),
|
|
@@ -318,7 +315,16 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
318
315
|
noble.emit('discover', freshPeripheral);
|
|
319
316
|
}),
|
|
320
317
|
stopScanning: jest.fn(callback => callback?.()),
|
|
321
|
-
connectAsync: jest.fn(() =>
|
|
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
|
+
),
|
|
322
328
|
});
|
|
323
329
|
jest.doMock('@stoprocent/noble', () => noble);
|
|
324
330
|
jest.doMock('electron', () => ({
|
|
@@ -352,87 +358,6 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
352
358
|
}
|
|
353
359
|
);
|
|
354
360
|
|
|
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
|
-
|
|
436
361
|
test('waits for a targeted scan to stop before connecting', async () => {
|
|
437
362
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
438
363
|
|
|
@@ -1006,6 +931,59 @@ describe('Noble BLE process shutdown', () => {
|
|
|
1006
931
|
expect(handlers.size).toBe(0);
|
|
1007
932
|
});
|
|
1008
933
|
|
|
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
|
+
|
|
1009
987
|
test('cancels an active scan, ignores its late callback and releases native once', async () => {
|
|
1010
988
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
1011
989
|
const { sdk, native, handlers } = await setup();
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
ONEKEY_WRITE_CHARACTERISTIC_UUID,
|
|
17
17
|
createKnownBleUuidAliases,
|
|
18
18
|
hasOnekeyCommunicationService,
|
|
19
|
-
isBleStaleBondHardwareError,
|
|
20
19
|
isOnekeyBluetoothDevice,
|
|
21
20
|
isPro2FamilyBleName,
|
|
22
21
|
matchesKnownBleUuid,
|
|
@@ -74,19 +73,6 @@ type NobleBleNativeError = Error & {
|
|
|
74
73
|
|
|
75
74
|
export function createNobleBleConnectionError(error: NobleBleNativeError, messagePrefix = '') {
|
|
76
75
|
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
|
-
|
|
90
76
|
return ERRORS.TypedError(HardwareErrorCode.BleConnectedError, `${messagePrefix}${errorMessage}`);
|
|
91
77
|
}
|
|
92
78
|
|
|
@@ -443,7 +429,18 @@ function updateBluetoothState(state: string): void {
|
|
|
443
429
|
// Initialize Noble
|
|
444
430
|
async function initializeNoble(): Promise<void> {
|
|
445
431
|
assertBleActive();
|
|
446
|
-
if (noble)
|
|
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
|
+
}
|
|
447
444
|
|
|
448
445
|
try {
|
|
449
446
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
@@ -496,7 +493,7 @@ async function initializeNoble(): Promise<void> {
|
|
|
496
493
|
reject(ERRORS.TypedError(HardwareErrorCode.BlePoweredOff));
|
|
497
494
|
} else if (state === 'unauthorized') {
|
|
498
495
|
cleanup();
|
|
499
|
-
reject(ERRORS.TypedError(HardwareErrorCode.
|
|
496
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BleLocationError));
|
|
500
497
|
}
|
|
501
498
|
};
|
|
502
499
|
|
|
@@ -508,6 +505,7 @@ async function initializeNoble(): Promise<void> {
|
|
|
508
505
|
};
|
|
509
506
|
pendingCancellations.add(cancel);
|
|
510
507
|
noble.on('stateChange', onStateChange);
|
|
508
|
+
onStateChange(noble.state);
|
|
511
509
|
});
|
|
512
510
|
|
|
513
511
|
assertBleActive();
|
|
@@ -522,7 +520,7 @@ async function initializeNoble(): Promise<void> {
|
|
|
522
520
|
logger?.info('[NobleBLE] Noble initialized successfully');
|
|
523
521
|
} catch (error) {
|
|
524
522
|
logger?.error('[NobleBLE] Failed to initialize Noble:', error);
|
|
525
|
-
bluetoothState.unsupported =
|
|
523
|
+
bluetoothState.unsupported = noble?.state === 'unsupported';
|
|
526
524
|
bluetoothState.initialized = true;
|
|
527
525
|
throw error;
|
|
528
526
|
}
|
|
@@ -1151,9 +1149,7 @@ async function performTargetedScan(
|
|
|
1151
1149
|
|
|
1152
1150
|
// Enumerate devices
|
|
1153
1151
|
async function enumerateDevices(isWindowDestroyed: () => boolean): Promise<DeviceInfo[]> {
|
|
1154
|
-
|
|
1155
|
-
await initializeNoble();
|
|
1156
|
-
}
|
|
1152
|
+
await initializeNoble();
|
|
1157
1153
|
|
|
1158
1154
|
if (!noble) {
|
|
1159
1155
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
@@ -1634,7 +1630,6 @@ async function setupConnectionAndDiscoverServices(
|
|
|
1634
1630
|
await forceReconnectPeripheral(peripheral, deviceId);
|
|
1635
1631
|
} catch (resetError) {
|
|
1636
1632
|
if (
|
|
1637
|
-
isBleStaleBondHardwareError(resetError) ||
|
|
1638
1633
|
(resetError as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleDeviceDisconnected
|
|
1639
1634
|
) {
|
|
1640
1635
|
throw resetError;
|
|
@@ -1745,10 +1740,6 @@ async function tryDirectConnectById(deviceId: string): Promise<Peripheral | unde
|
|
|
1745
1740
|
deviceId,
|
|
1746
1741
|
error: String(error),
|
|
1747
1742
|
});
|
|
1748
|
-
const nativeError = error as NobleBleNativeError;
|
|
1749
|
-
if (nativeError.nativeErrorCode === 14 && nativeError.nativeErrorDomain === 'CBErrorDomain') {
|
|
1750
|
-
throw createNobleBleConnectionError(nativeError);
|
|
1751
|
-
}
|
|
1752
1743
|
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1753
1744
|
return undefined;
|
|
1754
1745
|
} finally {
|
|
@@ -1759,6 +1750,7 @@ async function tryDirectConnectById(deviceId: string): Promise<Peripheral | unde
|
|
|
1759
1750
|
|
|
1760
1751
|
// Connect to device - supports both discovered and direct connection modes
|
|
1761
1752
|
async function connectDevice(deviceId: string, webContents: WebContents): Promise<void> {
|
|
1753
|
+
await initializeNoble();
|
|
1762
1754
|
logger?.info('[NobleBLE] Connect device request:', {
|
|
1763
1755
|
deviceId,
|
|
1764
1756
|
hasDiscovered: discoveredDevices.has(deviceId),
|
|
@@ -1776,11 +1768,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1776
1768
|
}
|
|
1777
1769
|
|
|
1778
1770
|
if (!peripheral) {
|
|
1779
|
-
// Initialize Noble if not already done
|
|
1780
|
-
if (!noble) {
|
|
1781
|
-
await initializeNoble();
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1771
|
if (!noble) {
|
|
1785
1772
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1786
1773
|
}
|
|
@@ -1812,21 +1799,12 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1812
1799
|
}
|
|
1813
1800
|
};
|
|
1814
1801
|
|
|
1815
|
-
let staleBondError: Error | undefined;
|
|
1816
1802
|
const connectById = async () => {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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;
|
|
1803
|
+
const found = await tryDirectConnectById(deviceId);
|
|
1804
|
+
if (found) {
|
|
1805
|
+
discoveredDevices.set(deviceId, found);
|
|
1829
1806
|
}
|
|
1807
|
+
return found;
|
|
1830
1808
|
};
|
|
1831
1809
|
|
|
1832
1810
|
peripheral = byIdFirst ? await connectById() : await scanForPeripheral();
|
|
@@ -1835,7 +1813,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1835
1813
|
// silent), or not reachable by id. Try the other one before giving up.
|
|
1836
1814
|
peripheral = byIdFirst ? await scanForPeripheral() : await connectById();
|
|
1837
1815
|
}
|
|
1838
|
-
if (!peripheral && staleBondError) throw staleBondError;
|
|
1839
1816
|
}
|
|
1840
1817
|
|
|
1841
1818
|
assertBleActive();
|