@onekeyfe/hd-transport-electron 1.2.0-alpha.166 → 1.2.0-alpha.168
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-f4b406db.js → index-b37d926c.js} +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-038532d9.js → noble-ble-handler-e8b87173.js} +40 -95
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/types/noble-extended.d.ts +0 -1
- package/dist/types/noble-extended.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/noble-ble-handler.ts +52 -184
- package/src/types/noble-extended.ts +0 -1
|
@@ -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-e8b87173.js'); });
|
|
36
36
|
setupNobleBleHandlers(webContents);
|
|
37
37
|
});
|
|
38
38
|
}
|
package/dist/index.d.ts
CHANGED
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-b37d926c.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
|
|
@@ -116,8 +116,7 @@ const DEVICE_SCAN_TIMEOUT = 5000;
|
|
|
116
116
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
117
117
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
118
118
|
const BLE_CLEANUP_TIMEOUT = 250;
|
|
119
|
-
const
|
|
120
|
-
const BLE_IDLE_DISCONNECT_MS = 20000;
|
|
119
|
+
const BLE_IDLE_DISCONNECT_MS = 3 * 60000;
|
|
121
120
|
const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
|
|
122
121
|
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
123
122
|
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
@@ -243,7 +242,6 @@ function initializeNoble() {
|
|
|
243
242
|
try {
|
|
244
243
|
noble = require('@stoprocent/noble');
|
|
245
244
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
|
|
246
|
-
setupPersistentStateListener();
|
|
247
245
|
yield new Promise((resolve, reject) => {
|
|
248
246
|
if (!noble) {
|
|
249
247
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not initialized'));
|
|
@@ -253,6 +251,7 @@ function initializeNoble() {
|
|
|
253
251
|
resolve();
|
|
254
252
|
return;
|
|
255
253
|
}
|
|
254
|
+
setupPersistentStateListener();
|
|
256
255
|
const timeout = setTimeout(() => {
|
|
257
256
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
|
|
258
257
|
}, BLUETOOTH_INIT_TIMEOUT);
|
|
@@ -337,14 +336,7 @@ function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle
|
|
|
337
336
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device', { deviceId, reason });
|
|
338
337
|
const peripheral = connectedDevices.get(deviceId);
|
|
339
338
|
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
340
|
-
const pending =
|
|
341
|
-
.catch(error => {
|
|
342
|
-
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Keep-alive unsubscribe failed, disconnecting anyway', {
|
|
343
|
-
deviceId,
|
|
344
|
-
error: error instanceof Error ? error.message : String(error),
|
|
345
|
-
});
|
|
346
|
-
})
|
|
347
|
-
.then(() => disconnectDevice(deviceId))
|
|
339
|
+
const pending = disconnectDevice(deviceId)
|
|
348
340
|
.then(() => {
|
|
349
341
|
broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
|
|
350
342
|
id: deviceId,
|
|
@@ -396,10 +388,6 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
396
388
|
pairedDevices.delete(deviceId);
|
|
397
389
|
}
|
|
398
390
|
if (cleanupDiscoveredCache) {
|
|
399
|
-
const evicted = discoveredDevices.get(deviceId);
|
|
400
|
-
if (evicted) {
|
|
401
|
-
stalePeripherals.set(deviceId, evicted);
|
|
402
|
-
}
|
|
403
391
|
discoveredDevices.delete(deviceId);
|
|
404
392
|
}
|
|
405
393
|
if (sendDisconnectEvent) {
|
|
@@ -421,7 +409,6 @@ function handleDeviceDisconnect(deviceId, webContents) {
|
|
|
421
409
|
});
|
|
422
410
|
cleanupDevice(deviceId, webContents, {
|
|
423
411
|
cleanupConnection: true,
|
|
424
|
-
cleanupDiscoveredCache: true,
|
|
425
412
|
sendDisconnectEvent: true,
|
|
426
413
|
cancelOperations: true,
|
|
427
414
|
reason: 'auto-disconnect',
|
|
@@ -603,37 +590,18 @@ function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
|
603
590
|
}
|
|
604
591
|
});
|
|
605
592
|
}
|
|
606
|
-
const BLE_COLD_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
607
|
-
const stalePeripherals = new Map();
|
|
608
|
-
const bleNamesById = new Map();
|
|
609
|
-
function rememberBleName(deviceId, name) {
|
|
610
|
-
const trimmed = name === null || name === void 0 ? void 0 : name.trim();
|
|
611
|
-
if (trimmed) {
|
|
612
|
-
bleNamesById.set(deviceId, trimmed);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
function resolveBleName(deviceId) {
|
|
616
|
-
var _a, _b, _c, _d, _e, _f;
|
|
617
|
-
const live = ((_c = (_b = (_a = connectedDevices.get(deviceId)) === null || _a === void 0 ? void 0 : _a.advertisement) === null || _b === void 0 ? void 0 : _b.localName) === null || _c === void 0 ? void 0 : _c.trim()) ||
|
|
618
|
-
((_f = (_e = (_d = discoveredDevices.get(deviceId)) === null || _d === void 0 ? void 0 : _d.advertisement) === null || _e === void 0 ? void 0 : _e.localName) === null || _f === void 0 ? void 0 : _f.trim());
|
|
619
|
-
return live || bleNamesById.get(deviceId);
|
|
620
|
-
}
|
|
621
|
-
function shouldConnectByIdFirst(deviceId) {
|
|
622
|
-
return hdShared.isPro2FamilyBleName(resolveBleName(deviceId));
|
|
623
|
-
}
|
|
624
593
|
function handleDeviceDiscovered(peripheral) {
|
|
625
|
-
var _a, _b
|
|
594
|
+
var _a, _b;
|
|
626
595
|
if (!isOneKeyPeripheral(peripheral)) {
|
|
627
596
|
return;
|
|
628
597
|
}
|
|
629
598
|
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
630
599
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
631
|
-
rememberBleName(peripheral.id, (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName);
|
|
632
600
|
if (isNewDevice) {
|
|
633
601
|
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
634
602
|
deviceId: peripheral.id,
|
|
635
|
-
name: ((
|
|
636
|
-
serviceUUIDs: ((
|
|
603
|
+
name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
|
|
604
|
+
serviceUUIDs: ((_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.serviceUuids) || [],
|
|
637
605
|
});
|
|
638
606
|
}
|
|
639
607
|
}
|
|
@@ -659,7 +627,7 @@ function waitForNobleScanStop(nobleInstance) {
|
|
|
659
627
|
});
|
|
660
628
|
});
|
|
661
629
|
}
|
|
662
|
-
function performTargetedScan(targetDeviceId
|
|
630
|
+
function performTargetedScan(targetDeviceId) {
|
|
663
631
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
664
632
|
if (!noble) {
|
|
665
633
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
@@ -699,7 +667,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
699
667
|
const timeoutId = setTimeout(() => {
|
|
700
668
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
701
669
|
finish(null).catch(reject);
|
|
702
|
-
},
|
|
670
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
703
671
|
nobleInstance.on('discover', onDiscover);
|
|
704
672
|
nobleInstance.startScanning([], true, (error) => {
|
|
705
673
|
if (error) {
|
|
@@ -914,12 +882,7 @@ function forceReconnectPeripheral(peripheral, deviceId) {
|
|
|
914
882
|
yield runBleCallbackOperation(callback => peripheral.disconnect(() => {
|
|
915
883
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
|
|
916
884
|
callback();
|
|
917
|
-
}), { timeoutMs:
|
|
918
|
-
if (peripheral.state === 'connected') {
|
|
919
|
-
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Reset disconnect did not confirm, reconnecting anyway', {
|
|
920
|
-
deviceId,
|
|
921
|
-
});
|
|
922
|
-
}
|
|
885
|
+
}), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
|
|
923
886
|
}
|
|
924
887
|
yield runBleCallbackOperation(callback => peripheral.connect(callback), {
|
|
925
888
|
timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
|
|
@@ -1003,18 +966,23 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
1003
966
|
}
|
|
1004
967
|
function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
1005
968
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
969
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1006
970
|
try {
|
|
1007
|
-
yield
|
|
971
|
+
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1008
972
|
}
|
|
1009
|
-
catch (
|
|
1010
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
973
|
+
catch (initialError) {
|
|
974
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
|
|
975
|
+
deviceId,
|
|
976
|
+
error: initialError,
|
|
977
|
+
});
|
|
1011
978
|
}
|
|
979
|
+
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
1012
980
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1013
981
|
try {
|
|
1014
982
|
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1015
983
|
}
|
|
1016
|
-
catch (
|
|
1017
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...',
|
|
984
|
+
catch (reconnectError) {
|
|
985
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...', reconnectError);
|
|
1018
986
|
return freshScanAndDiscover(deviceId, webContents);
|
|
1019
987
|
}
|
|
1020
988
|
});
|
|
@@ -1074,7 +1042,7 @@ function tryDirectConnectById(deviceId) {
|
|
|
1074
1042
|
});
|
|
1075
1043
|
}
|
|
1076
1044
|
function connectDevice(deviceId, webContents) {
|
|
1077
|
-
var _a
|
|
1045
|
+
var _a;
|
|
1078
1046
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1079
1047
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
1080
1048
|
deviceId,
|
|
@@ -1086,57 +1054,34 @@ function connectDevice(deviceId, webContents) {
|
|
|
1086
1054
|
});
|
|
1087
1055
|
let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
|
|
1088
1056
|
if (!peripheral) {
|
|
1057
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
1089
1058
|
if (!noble) {
|
|
1090
1059
|
yield initializeNoble();
|
|
1091
1060
|
}
|
|
1092
1061
|
if (!noble) {
|
|
1093
1062
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1094
1063
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
deviceId,
|
|
1098
|
-
name: (_b = resolveBleName(deviceId)) !== null && _b !== void 0 ? _b : 'unknown',
|
|
1099
|
-
strategy: byIdFirst ? 'connect-by-id-first' : 'scan-first',
|
|
1100
|
-
});
|
|
1101
|
-
const scanForPeripheral = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1102
|
-
var _d;
|
|
1103
|
-
try {
|
|
1104
|
-
return (_d = (yield performTargetedScan(deviceId, BLE_COLD_CONNECT_SCAN_TIMEOUT_MS))) !== null && _d !== void 0 ? _d : undefined;
|
|
1105
|
-
}
|
|
1106
|
-
catch (scanError) {
|
|
1107
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan failed', {
|
|
1108
|
-
deviceId,
|
|
1109
|
-
error: scanError instanceof Error ? scanError.message : String(scanError),
|
|
1110
|
-
});
|
|
1111
|
-
return undefined;
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
const connectById = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1115
|
-
const found = yield tryDirectConnectById(deviceId);
|
|
1116
|
-
if (found) {
|
|
1117
|
-
discoveredDevices.set(deviceId, found);
|
|
1118
|
-
}
|
|
1119
|
-
return found;
|
|
1120
|
-
});
|
|
1121
|
-
peripheral = byIdFirst ? yield connectById() : yield scanForPeripheral();
|
|
1122
|
-
if (!peripheral) {
|
|
1123
|
-
peripheral = byIdFirst ? yield scanForPeripheral() : yield connectById();
|
|
1064
|
+
peripheral = yield tryDirectConnectById(deviceId);
|
|
1065
|
+
if (peripheral) {
|
|
1066
|
+
discoveredDevices.set(deviceId, peripheral);
|
|
1124
1067
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1068
|
+
}
|
|
1069
|
+
if (!peripheral) {
|
|
1070
|
+
try {
|
|
1071
|
+
const foundPeripheral = yield performTargetedScan(deviceId);
|
|
1072
|
+
if (!foundPeripheral) {
|
|
1073
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found even after targeted scan`);
|
|
1130
1074
|
}
|
|
1075
|
+
peripheral = foundPeripheral;
|
|
1131
1076
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Targeted scan failed:', error);
|
|
1079
|
+
throw error;
|
|
1134
1080
|
}
|
|
1135
1081
|
}
|
|
1136
1082
|
if (!peripheral) {
|
|
1137
1083
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
|
|
1138
1084
|
}
|
|
1139
|
-
rememberBleName(deviceId, (_c = peripheral.advertisement) === null || _c === void 0 ? void 0 : _c.localName);
|
|
1140
1085
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connecting to device:', deviceId);
|
|
1141
1086
|
if (peripheral.state === 'connected') {
|
|
1142
1087
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
|
|
@@ -1231,15 +1176,11 @@ function disconnectDevice(deviceId) {
|
|
|
1231
1176
|
deviceDisconnectListeners.delete(deviceId);
|
|
1232
1177
|
}
|
|
1233
1178
|
yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1234
|
-
timeoutMs:
|
|
1179
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1235
1180
|
timeoutBehavior: 'resolve',
|
|
1236
1181
|
});
|
|
1237
|
-
if (peripheral.state === 'connected') {
|
|
1238
|
-
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Physical disconnect did not confirm in time', { deviceId });
|
|
1239
|
-
}
|
|
1240
1182
|
cleanupDevice(deviceId, undefined, {
|
|
1241
1183
|
cleanupConnection: true,
|
|
1242
|
-
cleanupDiscoveredCache: true,
|
|
1243
1184
|
sendDisconnectEvent: false,
|
|
1244
1185
|
cancelOperations: true,
|
|
1245
1186
|
reason: 'manual-disconnect',
|
|
@@ -1450,6 +1391,10 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1450
1391
|
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1451
1392
|
}
|
|
1452
1393
|
yield stopScanning().catch(() => undefined);
|
|
1394
|
+
if (noble && persistentStateListener) {
|
|
1395
|
+
noble.removeListener('stateChange', persistentStateListener);
|
|
1396
|
+
persistentStateListener = null;
|
|
1397
|
+
}
|
|
1453
1398
|
cleanupNobleListeners();
|
|
1454
1399
|
discoveredDevices.clear();
|
|
1455
1400
|
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|
|
@@ -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":"AA+BA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AA0EhE,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AA2mDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAgMpE"}
|
|
@@ -22,7 +22,6 @@ export interface NobleModule {
|
|
|
22
22
|
export interface Logger {
|
|
23
23
|
info(message: string, ...args: any[]): void;
|
|
24
24
|
debug(message: string, ...args: any[]): void;
|
|
25
|
-
warn(message: string, ...args: any[]): void;
|
|
26
25
|
error(message: string, ...args: any[]): void;
|
|
27
26
|
}
|
|
28
27
|
export declare function safeLog(logger: Logger | null, level: 'info' | 'debug' | 'error', message: string, ...args: any[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,
|
|
1
|
+
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-electron",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.168",
|
|
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.0-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.168",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.168",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.168",
|
|
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": "70f16e98ede0365f106ae8add83e82afff761c4c"
|
|
40
40
|
}
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
createKnownBleUuidAliases,
|
|
17
17
|
hasOnekeyCommunicationService,
|
|
18
18
|
isOnekeyBluetoothDevice,
|
|
19
|
-
isPro2FamilyBleName,
|
|
20
19
|
matchesKnownBleUuid,
|
|
21
20
|
wait,
|
|
22
21
|
} from '@onekeyfe/hd-shared';
|
|
@@ -91,18 +90,8 @@ const DEVICE_SCAN_TIMEOUT = 5000; // 5 seconds for device scanning
|
|
|
91
90
|
const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
|
|
92
91
|
const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
|
|
93
92
|
const BLE_CLEANUP_TIMEOUT = 250;
|
|
94
|
-
// A physical teardown must actually reach the OS before the device can return
|
|
95
|
-
// to a clean state; 250ms routinely declared success while CoreBluetooth was
|
|
96
|
-
// still disconnecting, leaving no trace when the teardown never completed.
|
|
97
|
-
const BLE_DISCONNECT_CONFIRM_TIMEOUT_MS = 3000;
|
|
98
93
|
// Renderer release is logical only; this timer physically frees the device.
|
|
99
|
-
|
|
100
|
-
// after sitting idle for minutes (field logs 2026-08-19: every deaf window
|
|
101
|
-
// followed a 3-minute-idle disconnect, while disconnects right after traffic
|
|
102
|
-
// have never produced one across 6.5.0's per-call teardown history). A hot
|
|
103
|
-
// disconnect ~20s after the last operation stays inside the proven-safe
|
|
104
|
-
// pattern and also shrinks the window in which phones cannot see the device.
|
|
105
|
-
const BLE_IDLE_DISCONNECT_MS = 20_000;
|
|
94
|
+
const BLE_IDLE_DISCONNECT_MS = 3 * 60_000;
|
|
106
95
|
// Ceiling while a call is in flight: no outstanding write, but not forever.
|
|
107
96
|
const BLE_BUSY_BACKSTOP_MS = 10 * 60_000;
|
|
108
97
|
|
|
@@ -268,11 +257,6 @@ async function initializeNoble(): Promise<void> {
|
|
|
268
257
|
noble = require('@stoprocent/noble') as NobleModule;
|
|
269
258
|
logger?.info('[NobleBLE] Noble library loaded');
|
|
270
259
|
|
|
271
|
-
// Register the process-lifetime state listener before any early return:
|
|
272
|
-
// the poweredOn fast path below would otherwise skip it for the whole
|
|
273
|
-
// session, leaving poweredOff cache/state reconciliation dead.
|
|
274
|
-
setupPersistentStateListener();
|
|
275
|
-
|
|
276
260
|
// Wait for Bluetooth to be ready
|
|
277
261
|
await new Promise<void>((resolve, reject) => {
|
|
278
262
|
if (!noble) {
|
|
@@ -285,6 +269,9 @@ async function initializeNoble(): Promise<void> {
|
|
|
285
269
|
return;
|
|
286
270
|
}
|
|
287
271
|
|
|
272
|
+
// Setup persistent state listener before initialization
|
|
273
|
+
setupPersistentStateListener();
|
|
274
|
+
|
|
288
275
|
const timeout = setTimeout(() => {
|
|
289
276
|
reject(
|
|
290
277
|
ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout')
|
|
@@ -411,21 +398,7 @@ function armIdleDisconnect(
|
|
|
411
398
|
logger?.info('[NobleBLE] Keep-alive timeout, disconnecting device', { deviceId, reason });
|
|
412
399
|
const peripheral = connectedDevices.get(deviceId);
|
|
413
400
|
const deviceName = peripheral?.advertisement?.localName || 'Unknown Device';
|
|
414
|
-
|
|
415
|
-
// teardown path: the 1S leaves its notify session half-open when the
|
|
416
|
-
// link drops without an unsubscribe and then ignores application
|
|
417
|
-
// protocol traffic on NEW links for tens of minutes (field log
|
|
418
|
-
// 2026-08-19: the lone unsubscribe-skipping idle teardown caused a
|
|
419
|
-
// 6-attempt reconnect loop; unsubscribe-first teardowns reconnected
|
|
420
|
-
// instantly).
|
|
421
|
-
const pending = unsubscribeNotifications(deviceId)
|
|
422
|
-
.catch(error => {
|
|
423
|
-
logger?.warn('[NobleBLE] Keep-alive unsubscribe failed, disconnecting anyway', {
|
|
424
|
-
deviceId,
|
|
425
|
-
error: error instanceof Error ? error.message : String(error),
|
|
426
|
-
});
|
|
427
|
-
})
|
|
428
|
-
.then(() => disconnectDevice(deviceId))
|
|
401
|
+
const pending = disconnectDevice(deviceId)
|
|
429
402
|
.then(() => {
|
|
430
403
|
// A call is still in flight here; it must reject, not hang.
|
|
431
404
|
// Both 'idle' and 'busy-backstop' report IdleKeepAlive on purpose:
|
|
@@ -503,16 +476,8 @@ function cleanupDevice(
|
|
|
503
476
|
pairedDevices.delete(deviceId);
|
|
504
477
|
}
|
|
505
478
|
|
|
506
|
-
// 2. Clean up discovered cache (optional)
|
|
507
|
-
// through a disconnect is never preferred again — reconnecting it is what
|
|
508
|
-
// produces stale-session links — but it is kept aside as the last resort for
|
|
509
|
-
// a bonded device that stays silent, so eviction cannot turn a working
|
|
510
|
-
// reconnect into DeviceNotFound.
|
|
479
|
+
// 2. Clean up discovered cache (optional)
|
|
511
480
|
if (cleanupDiscoveredCache) {
|
|
512
|
-
const evicted = discoveredDevices.get(deviceId);
|
|
513
|
-
if (evicted) {
|
|
514
|
-
stalePeripherals.set(deviceId, evicted);
|
|
515
|
-
}
|
|
516
481
|
discoveredDevices.delete(deviceId);
|
|
517
482
|
}
|
|
518
483
|
|
|
@@ -543,9 +508,6 @@ function handleDeviceDisconnect(deviceId: string, webContents: WebContents): voi
|
|
|
543
508
|
|
|
544
509
|
cleanupDevice(deviceId, webContents, {
|
|
545
510
|
cleanupConnection: true,
|
|
546
|
-
// Same stale-object hazard as manual disconnect: an externally dropped
|
|
547
|
-
// link invalidates the cached peripheral for the next reconnect.
|
|
548
|
-
cleanupDiscoveredCache: true,
|
|
549
511
|
sendDisconnectEvent: true,
|
|
550
512
|
cancelOperations: true,
|
|
551
513
|
reason: 'auto-disconnect',
|
|
@@ -799,45 +761,6 @@ async function transmitHexDataToDevice(
|
|
|
799
761
|
}
|
|
800
762
|
|
|
801
763
|
// Handle discovered device (for general enumeration only)
|
|
802
|
-
// A scan that finds nothing costs this much before the direct-connect
|
|
803
|
-
// fallback runs. Every advertisement in the 6.5.0 control logs arrived within
|
|
804
|
-
// 631ms, so this leaves ~2x headroom while keeping a miss cheap.
|
|
805
|
-
const BLE_COLD_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
806
|
-
|
|
807
|
-
// Peripherals evicted by a disconnect, kept only as the fallback of last
|
|
808
|
-
// resort in connectDevice (see cleanupDevice).
|
|
809
|
-
const stalePeripherals = new Map<string, Peripheral>();
|
|
810
|
-
|
|
811
|
-
// Advertised names, kept for the life of the process: device caches are purged
|
|
812
|
-
// on every disconnect, but the family a device belongs to never changes and
|
|
813
|
-
// decides which connection strategy is safe for it.
|
|
814
|
-
const bleNamesById = new Map<string, string>();
|
|
815
|
-
|
|
816
|
-
function rememberBleName(deviceId: string, name?: string | null): void {
|
|
817
|
-
const trimmed = name?.trim();
|
|
818
|
-
if (trimmed) {
|
|
819
|
-
bleNamesById.set(deviceId, trimmed);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function resolveBleName(deviceId: string): string | undefined {
|
|
824
|
-
const live =
|
|
825
|
-
connectedDevices.get(deviceId)?.advertisement?.localName?.trim() ||
|
|
826
|
-
discoveredDevices.get(deviceId)?.advertisement?.localName?.trim();
|
|
827
|
-
return live || bleNamesById.get(deviceId);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
/**
|
|
831
|
-
* Pro2/Neo connect by id first: they are unaffected by the stale-session
|
|
832
|
-
* problem and advertise under Find My names a OneKey-filtered scan cannot
|
|
833
|
-
* match. Everything else — Classic family, and any device whose name is not
|
|
834
|
-
* known yet — scans first, because connecting by id can resolve a session the
|
|
835
|
-
* device no longer serves (see setupConnectionAndDiscoverServices).
|
|
836
|
-
*/
|
|
837
|
-
function shouldConnectByIdFirst(deviceId: string): boolean {
|
|
838
|
-
return isPro2FamilyBleName(resolveBleName(deviceId));
|
|
839
|
-
}
|
|
840
|
-
|
|
841
764
|
function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
842
765
|
// Only process OneKey candidates for general discovery. Avoid logging every
|
|
843
766
|
// ambient BLE peripheral; it makes Pro2 debugging hard to read.
|
|
@@ -847,7 +770,6 @@ function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
|
847
770
|
|
|
848
771
|
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
849
772
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
850
|
-
rememberBleName(peripheral.id, peripheral.advertisement?.localName);
|
|
851
773
|
if (isNewDevice) {
|
|
852
774
|
logger?.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
853
775
|
deviceId: peripheral.id,
|
|
@@ -882,10 +804,7 @@ async function waitForNobleScanStop(nobleInstance: NobleModule): Promise<void> {
|
|
|
882
804
|
|
|
883
805
|
// Perform targeted scan for a specific device ID
|
|
884
806
|
// Uses self-contained local listener pattern - no global state needed
|
|
885
|
-
async function performTargetedScan(
|
|
886
|
-
targetDeviceId: string,
|
|
887
|
-
timeoutMs: number = NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS
|
|
888
|
-
): Promise<Peripheral | null> {
|
|
807
|
+
async function performTargetedScan(targetDeviceId: string): Promise<Peripheral | null> {
|
|
889
808
|
if (!noble) {
|
|
890
809
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
891
810
|
}
|
|
@@ -930,7 +849,7 @@ async function performTargetedScan(
|
|
|
930
849
|
const timeoutId = setTimeout(() => {
|
|
931
850
|
logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
932
851
|
finish(null).catch(reject);
|
|
933
|
-
},
|
|
852
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
934
853
|
|
|
935
854
|
// Add local listener for this scan
|
|
936
855
|
nobleInstance.on('discover', onDiscover);
|
|
@@ -1237,10 +1156,7 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
|
|
|
1237
1156
|
reason: 'force-reconnect',
|
|
1238
1157
|
});
|
|
1239
1158
|
|
|
1240
|
-
// Step 2: Force disconnect if connected
|
|
1241
|
-
// confirmed: reconnecting while CoreBluetooth is still closing the old link
|
|
1242
|
-
// rebuilds the very stale-session state this reset exists to clear, and
|
|
1243
|
-
// 250ms is not enough to prove a physical teardown on macOS.
|
|
1159
|
+
// Step 2: Force disconnect if connected
|
|
1244
1160
|
if (peripheral.state === 'connected') {
|
|
1245
1161
|
await runBleCallbackOperation(
|
|
1246
1162
|
callback =>
|
|
@@ -1248,13 +1164,8 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
|
|
|
1248
1164
|
logger?.info('[NobleBLE] Force disconnect completed');
|
|
1249
1165
|
callback();
|
|
1250
1166
|
}),
|
|
1251
|
-
{ timeoutMs:
|
|
1167
|
+
{ timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' }
|
|
1252
1168
|
);
|
|
1253
|
-
if (peripheral.state === 'connected') {
|
|
1254
|
-
logger?.warn('[NobleBLE] Reset disconnect did not confirm, reconnecting anyway', {
|
|
1255
|
-
deviceId,
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
1169
|
}
|
|
1259
1170
|
|
|
1260
1171
|
// Step 3: Re-establish connection
|
|
@@ -1394,28 +1305,26 @@ async function setupConnectionAndDiscoverServices(
|
|
|
1394
1305
|
deviceId: string,
|
|
1395
1306
|
webContents: WebContents
|
|
1396
1307
|
): Promise<CharacteristicPair> {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
// (control log 2026-08-20: 16/16 links answered within ~1s, including after
|
|
1400
|
-
// a 15-minute idle). Demoting it to a discovery-failure fallback removed the
|
|
1401
|
-
// reset entirely for this device, because discovery always succeeds: the link
|
|
1402
|
-
// comes up, GATT resolves from cache, and the device then answers nothing on
|
|
1403
|
-
// a session it no longer serves. Costs ~1.3s per cold setup; a kept-alive
|
|
1404
|
-
// link with an intact subscription returns before reaching here, so reuse
|
|
1405
|
-
// inside a workflow is unaffected.
|
|
1308
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1309
|
+
|
|
1406
1310
|
try {
|
|
1407
|
-
await
|
|
1408
|
-
} catch (
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1311
|
+
return await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1312
|
+
} catch (initialError) {
|
|
1313
|
+
logger?.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
|
|
1314
|
+
deviceId,
|
|
1315
|
+
error: initialError,
|
|
1316
|
+
});
|
|
1412
1317
|
}
|
|
1413
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1414
1318
|
|
|
1319
|
+
await forceReconnectPeripheral(peripheral, deviceId);
|
|
1320
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1415
1321
|
try {
|
|
1416
1322
|
return await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1417
|
-
} catch (
|
|
1418
|
-
logger?.error(
|
|
1323
|
+
} catch (reconnectError) {
|
|
1324
|
+
logger?.error(
|
|
1325
|
+
'[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...',
|
|
1326
|
+
reconnectError
|
|
1327
|
+
);
|
|
1419
1328
|
return freshScanAndDiscover(deviceId, webContents);
|
|
1420
1329
|
}
|
|
1421
1330
|
}
|
|
@@ -1504,7 +1413,10 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1504
1413
|
// enumerate clears the discovery map; a kept-alive link outlives it.
|
|
1505
1414
|
let peripheral = discoveredDevices.get(deviceId) ?? connectedDevices.get(deviceId);
|
|
1506
1415
|
|
|
1416
|
+
// If device not discovered, try a targeted scan for this specific device
|
|
1507
1417
|
if (!peripheral) {
|
|
1418
|
+
logger?.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
1419
|
+
|
|
1508
1420
|
// Initialize Noble if not already done
|
|
1509
1421
|
if (!noble) {
|
|
1510
1422
|
await initializeNoble();
|
|
@@ -1514,58 +1426,27 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1514
1426
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1515
1427
|
}
|
|
1516
1428
|
|
|
1517
|
-
//
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
// two when the device does advertise: ~96ms median against ~784ms for
|
|
1522
|
-
// connect-by-id across the field logs. Pro2/Neo keep connect-by-id first
|
|
1523
|
-
// because they are unaffected and may advertise under an unmatchable Find
|
|
1524
|
-
// My name.
|
|
1525
|
-
const byIdFirst = shouldConnectByIdFirst(deviceId);
|
|
1526
|
-
logger?.info('[NobleBLE] Resolving device for cold connect', {
|
|
1527
|
-
deviceId,
|
|
1528
|
-
name: resolveBleName(deviceId) ?? 'unknown',
|
|
1529
|
-
strategy: byIdFirst ? 'connect-by-id-first' : 'scan-first',
|
|
1530
|
-
});
|
|
1531
|
-
|
|
1532
|
-
const scanForPeripheral = async () => {
|
|
1533
|
-
try {
|
|
1534
|
-
return (await performTargetedScan(deviceId, BLE_COLD_CONNECT_SCAN_TIMEOUT_MS)) ?? undefined;
|
|
1535
|
-
} catch (scanError) {
|
|
1536
|
-
logger?.info('[NobleBLE] Targeted scan failed', {
|
|
1537
|
-
deviceId,
|
|
1538
|
-
error: scanError instanceof Error ? scanError.message : String(scanError),
|
|
1539
|
-
});
|
|
1540
|
-
return undefined;
|
|
1541
|
-
}
|
|
1542
|
-
};
|
|
1543
|
-
|
|
1544
|
-
const connectById = async () => {
|
|
1545
|
-
const found = await tryDirectConnectById(deviceId);
|
|
1546
|
-
if (found) {
|
|
1547
|
-
discoveredDevices.set(deviceId, found);
|
|
1548
|
-
}
|
|
1549
|
-
return found;
|
|
1550
|
-
};
|
|
1551
|
-
|
|
1552
|
-
peripheral = byIdFirst ? await connectById() : await scanForPeripheral();
|
|
1553
|
-
if (!peripheral) {
|
|
1554
|
-
// The preferred route came up empty: not advertising (held elsewhere, or
|
|
1555
|
-
// silent), or not reachable by id. Try the other one before giving up.
|
|
1556
|
-
peripheral = byIdFirst ? await scanForPeripheral() : await connectById();
|
|
1429
|
+
// Reaches a bonded device that is not advertising; else falls to the scan.
|
|
1430
|
+
peripheral = await tryDirectConnectById(deviceId);
|
|
1431
|
+
if (peripheral) {
|
|
1432
|
+
discoveredDevices.set(deviceId, peripheral);
|
|
1557
1433
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (!peripheral) {
|
|
1437
|
+
// Perform a targeted scan to find the specific device
|
|
1438
|
+
try {
|
|
1439
|
+
const foundPeripheral = await performTargetedScan(deviceId);
|
|
1440
|
+
if (!foundPeripheral) {
|
|
1441
|
+
throw ERRORS.TypedError(
|
|
1442
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1443
|
+
`Device ${deviceId} not found even after targeted scan`
|
|
1444
|
+
);
|
|
1566
1445
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1446
|
+
peripheral = foundPeripheral;
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
logger?.error('[NobleBLE] Targeted scan failed:', error);
|
|
1449
|
+
throw error;
|
|
1569
1450
|
}
|
|
1570
1451
|
}
|
|
1571
1452
|
|
|
@@ -1574,10 +1455,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1574
1455
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
|
|
1575
1456
|
}
|
|
1576
1457
|
|
|
1577
|
-
// Also covers the connect-by-id route, whose simulated discovery carries no
|
|
1578
|
-
// service UUIDs and therefore never reaches handleDeviceDiscovered.
|
|
1579
|
-
rememberBleName(deviceId, peripheral.advertisement?.localName);
|
|
1580
|
-
|
|
1581
1458
|
logger?.info('[NobleBLE] Connecting to device:', deviceId);
|
|
1582
1459
|
|
|
1583
1460
|
// Check if device is already connected
|
|
@@ -1720,18 +1597,11 @@ async function disconnectDevice(deviceId: string): Promise<void> {
|
|
|
1720
1597
|
}
|
|
1721
1598
|
|
|
1722
1599
|
await runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1723
|
-
timeoutMs:
|
|
1600
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1724
1601
|
timeoutBehavior: 'resolve',
|
|
1725
1602
|
});
|
|
1726
|
-
if (peripheral.state === 'connected') {
|
|
1727
|
-
logger?.warn('[NobleBLE] Physical disconnect did not confirm in time', { deviceId });
|
|
1728
|
-
}
|
|
1729
1603
|
cleanupDevice(deviceId, undefined, {
|
|
1730
1604
|
cleanupConnection: true,
|
|
1731
|
-
// macOS returns zero GATT services when a previously-disconnected
|
|
1732
|
-
// peripheral object is reconnected; drop the discovery cache so the next
|
|
1733
|
-
// connect resolves a fresh peripheral (direct connect by id, or scan).
|
|
1734
|
-
cleanupDiscoveredCache: true,
|
|
1735
1605
|
sendDisconnectEvent: false,
|
|
1736
1606
|
cancelOperations: true,
|
|
1737
1607
|
reason: 'manual-disconnect',
|
|
@@ -2057,12 +1927,10 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
2057
1927
|
}
|
|
2058
1928
|
|
|
2059
1929
|
await stopScanning().catch(() => undefined);
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
// initializeNoble early-returns once noble is loaded). The null-guard
|
|
2065
|
-
// in setupPersistentStateListener keeps it deduped to one instance.
|
|
1930
|
+
if (noble && persistentStateListener) {
|
|
1931
|
+
noble.removeListener('stateChange', persistentStateListener);
|
|
1932
|
+
persistentStateListener = null;
|
|
1933
|
+
}
|
|
2066
1934
|
cleanupNobleListeners();
|
|
2067
1935
|
discoveredDevices.clear();
|
|
2068
1936
|
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|