@onekeyfe/hd-transport-electron 1.2.0-alpha.174 → 1.2.0-alpha.176
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-2b9e4f42.js → index-b37d926c.js} +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-0fb76826.js → noble-ble-handler-e8b87173.js} +43 -97
- 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 -181
- 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,
|
|
@@ -417,7 +409,6 @@ function handleDeviceDisconnect(deviceId, webContents) {
|
|
|
417
409
|
});
|
|
418
410
|
cleanupDevice(deviceId, webContents, {
|
|
419
411
|
cleanupConnection: true,
|
|
420
|
-
cleanupDiscoveredCache: true,
|
|
421
412
|
sendDisconnectEvent: true,
|
|
422
413
|
cancelOperations: true,
|
|
423
414
|
reason: 'auto-disconnect',
|
|
@@ -545,7 +536,6 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
545
536
|
});
|
|
546
537
|
}
|
|
547
538
|
function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
548
|
-
var _a;
|
|
549
539
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
550
540
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
551
541
|
const peripheral = connectedDevices.get(deviceId);
|
|
@@ -557,14 +547,6 @@ function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
|
557
547
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
558
548
|
const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
559
549
|
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
560
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Write frame', {
|
|
561
|
-
deviceId,
|
|
562
|
-
bytes: toBuffer.length,
|
|
563
|
-
head: toBuffer.subarray(0, 8).toString('hex'),
|
|
564
|
-
packetCapacity,
|
|
565
|
-
mtu: (_a = peripheral.mtu) !== null && _a !== void 0 ? _a : null,
|
|
566
|
-
pacingDelayMs,
|
|
567
|
-
});
|
|
568
550
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
569
551
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
570
552
|
if (!writeCharacteristic) {
|
|
@@ -608,36 +590,18 @@ function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
|
608
590
|
}
|
|
609
591
|
});
|
|
610
592
|
}
|
|
611
|
-
const BLE_COLD_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
612
|
-
const bleNamesById = new Map();
|
|
613
|
-
function rememberBleName(deviceId, name) {
|
|
614
|
-
const trimmed = name === null || name === void 0 ? void 0 : name.trim();
|
|
615
|
-
if (trimmed) {
|
|
616
|
-
bleNamesById.set(deviceId, trimmed);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
function resolveBleName(deviceId) {
|
|
620
|
-
var _a, _b, _c, _d, _e, _f;
|
|
621
|
-
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()) ||
|
|
622
|
-
((_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());
|
|
623
|
-
return live || bleNamesById.get(deviceId);
|
|
624
|
-
}
|
|
625
|
-
function shouldConnectByIdFirst(deviceId) {
|
|
626
|
-
return hdShared.isPro2FamilyBleName(resolveBleName(deviceId));
|
|
627
|
-
}
|
|
628
593
|
function handleDeviceDiscovered(peripheral) {
|
|
629
|
-
var _a, _b
|
|
594
|
+
var _a, _b;
|
|
630
595
|
if (!isOneKeyPeripheral(peripheral)) {
|
|
631
596
|
return;
|
|
632
597
|
}
|
|
633
598
|
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
634
599
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
635
|
-
rememberBleName(peripheral.id, (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName);
|
|
636
600
|
if (isNewDevice) {
|
|
637
601
|
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
638
602
|
deviceId: peripheral.id,
|
|
639
|
-
name: ((
|
|
640
|
-
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) || [],
|
|
641
605
|
});
|
|
642
606
|
}
|
|
643
607
|
}
|
|
@@ -663,7 +627,7 @@ function waitForNobleScanStop(nobleInstance) {
|
|
|
663
627
|
});
|
|
664
628
|
});
|
|
665
629
|
}
|
|
666
|
-
function performTargetedScan(targetDeviceId
|
|
630
|
+
function performTargetedScan(targetDeviceId) {
|
|
667
631
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
668
632
|
if (!noble) {
|
|
669
633
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
@@ -703,7 +667,7 @@ function performTargetedScan(targetDeviceId, timeoutMs = NOBLE_BLE_TARGETED_SCAN
|
|
|
703
667
|
const timeoutId = setTimeout(() => {
|
|
704
668
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
705
669
|
finish(null).catch(reject);
|
|
706
|
-
},
|
|
670
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
707
671
|
nobleInstance.on('discover', onDiscover);
|
|
708
672
|
nobleInstance.startScanning([], true, (error) => {
|
|
709
673
|
if (error) {
|
|
@@ -838,7 +802,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
838
802
|
});
|
|
839
803
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
840
804
|
const services = yield new Promise((resolve, reject) => {
|
|
841
|
-
peripheral.discoverServices(
|
|
805
|
+
peripheral.discoverServices([], (error, svc) => {
|
|
842
806
|
if (error) {
|
|
843
807
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
|
|
844
808
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -849,7 +813,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
849
813
|
});
|
|
850
814
|
});
|
|
851
815
|
if (!services || services.length === 0) {
|
|
852
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No
|
|
816
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
853
817
|
}
|
|
854
818
|
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] services discovered', {
|
|
855
819
|
deviceId: peripheral.id,
|
|
@@ -1002,18 +966,23 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
1002
966
|
}
|
|
1003
967
|
function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
1004
968
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
969
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1005
970
|
try {
|
|
1006
|
-
yield
|
|
971
|
+
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1007
972
|
}
|
|
1008
|
-
catch (
|
|
1009
|
-
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
|
+
});
|
|
1010
978
|
}
|
|
979
|
+
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
1011
980
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1012
981
|
try {
|
|
1013
982
|
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1014
983
|
}
|
|
1015
|
-
catch (
|
|
1016
|
-
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);
|
|
1017
986
|
return freshScanAndDiscover(deviceId, webContents);
|
|
1018
987
|
}
|
|
1019
988
|
});
|
|
@@ -1073,7 +1042,7 @@ function tryDirectConnectById(deviceId) {
|
|
|
1073
1042
|
});
|
|
1074
1043
|
}
|
|
1075
1044
|
function connectDevice(deviceId, webContents) {
|
|
1076
|
-
var _a
|
|
1045
|
+
var _a;
|
|
1077
1046
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1078
1047
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
1079
1048
|
deviceId,
|
|
@@ -1085,47 +1054,34 @@ function connectDevice(deviceId, webContents) {
|
|
|
1085
1054
|
});
|
|
1086
1055
|
let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
|
|
1087
1056
|
if (!peripheral) {
|
|
1057
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
1088
1058
|
if (!noble) {
|
|
1089
1059
|
yield initializeNoble();
|
|
1090
1060
|
}
|
|
1091
1061
|
if (!noble) {
|
|
1092
1062
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1093
1063
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
deviceId,
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
catch (scanError) {
|
|
1106
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan failed', {
|
|
1107
|
-
deviceId,
|
|
1108
|
-
error: scanError instanceof Error ? scanError.message : String(scanError),
|
|
1109
|
-
});
|
|
1110
|
-
return undefined;
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
const connectById = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1114
|
-
const found = yield tryDirectConnectById(deviceId);
|
|
1115
|
-
if (found) {
|
|
1116
|
-
discoveredDevices.set(deviceId, found);
|
|
1064
|
+
peripheral = yield tryDirectConnectById(deviceId);
|
|
1065
|
+
if (peripheral) {
|
|
1066
|
+
discoveredDevices.set(deviceId, peripheral);
|
|
1067
|
+
}
|
|
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`);
|
|
1117
1074
|
}
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1075
|
+
peripheral = foundPeripheral;
|
|
1076
|
+
}
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Targeted scan failed:', error);
|
|
1079
|
+
throw error;
|
|
1123
1080
|
}
|
|
1124
1081
|
}
|
|
1125
1082
|
if (!peripheral) {
|
|
1126
1083
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
|
|
1127
1084
|
}
|
|
1128
|
-
rememberBleName(deviceId, (_c = peripheral.advertisement) === null || _c === void 0 ? void 0 : _c.localName);
|
|
1129
1085
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connecting to device:', deviceId);
|
|
1130
1086
|
if (peripheral.state === 'connected') {
|
|
1131
1087
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
|
|
@@ -1220,15 +1176,11 @@ function disconnectDevice(deviceId) {
|
|
|
1220
1176
|
deviceDisconnectListeners.delete(deviceId);
|
|
1221
1177
|
}
|
|
1222
1178
|
yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1223
|
-
timeoutMs:
|
|
1179
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1224
1180
|
timeoutBehavior: 'resolve',
|
|
1225
1181
|
});
|
|
1226
|
-
if (peripheral.state === 'connected') {
|
|
1227
|
-
logger === null || logger === void 0 ? void 0 : logger.warn('[NobleBLE] Physical disconnect did not confirm in time', { deviceId });
|
|
1228
|
-
}
|
|
1229
1182
|
cleanupDevice(deviceId, undefined, {
|
|
1230
1183
|
cleanupConnection: true,
|
|
1231
|
-
cleanupDiscoveredCache: true,
|
|
1232
1184
|
sendDisconnectEvent: false,
|
|
1233
1185
|
cancelOperations: true,
|
|
1234
1186
|
reason: 'manual-disconnect',
|
|
@@ -1311,11 +1263,6 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1311
1263
|
timeoutBehavior: 'reject',
|
|
1312
1264
|
});
|
|
1313
1265
|
notifyCharacteristic.on('data', (data) => {
|
|
1314
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Notify frame', {
|
|
1315
|
-
deviceId,
|
|
1316
|
-
bytes: data.length,
|
|
1317
|
-
head: data.subarray(0, 8).toString('hex'),
|
|
1318
|
-
});
|
|
1319
1266
|
if (!pairedDevices.has(deviceId)) {
|
|
1320
1267
|
pairedDevices.add(deviceId);
|
|
1321
1268
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device paired successfully', { deviceId });
|
|
@@ -1324,14 +1271,9 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1324
1271
|
});
|
|
1325
1272
|
});
|
|
1326
1273
|
}
|
|
1327
|
-
const subscribeStartedAt = Date.now();
|
|
1328
1274
|
try {
|
|
1329
1275
|
yield rebuildAppSubscription(deviceId, notifyCharacteristic);
|
|
1330
1276
|
subscribedDevices.set(deviceId, true);
|
|
1331
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification subscription active', {
|
|
1332
|
-
deviceId,
|
|
1333
|
-
ms: Date.now() - subscribeStartedAt,
|
|
1334
|
-
});
|
|
1335
1277
|
}
|
|
1336
1278
|
finally {
|
|
1337
1279
|
subscriptionOperations.set(deviceId, 'idle');
|
|
@@ -1449,6 +1391,10 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1449
1391
|
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1450
1392
|
}
|
|
1451
1393
|
yield stopScanning().catch(() => undefined);
|
|
1394
|
+
if (noble && persistentStateListener) {
|
|
1395
|
+
noble.removeListener('stateChange', persistentStateListener);
|
|
1396
|
+
persistentStateListener = null;
|
|
1397
|
+
}
|
|
1452
1398
|
cleanupNobleListeners();
|
|
1453
1399
|
discoveredDevices.clear();
|
|
1454
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.176",
|
|
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.176",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.176",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.176",
|
|
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": "a5152148317effe811cde8adecdfe75c28e82657"
|
|
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:
|
|
@@ -535,9 +508,6 @@ function handleDeviceDisconnect(deviceId: string, webContents: WebContents): voi
|
|
|
535
508
|
|
|
536
509
|
cleanupDevice(deviceId, webContents, {
|
|
537
510
|
cleanupConnection: true,
|
|
538
|
-
// Same stale-object hazard as manual disconnect: an externally dropped
|
|
539
|
-
// link invalidates the cached peripheral for the next reconnect.
|
|
540
|
-
cleanupDiscoveredCache: true,
|
|
541
511
|
sendDisconnectEvent: true,
|
|
542
512
|
cancelOperations: true,
|
|
543
513
|
reason: 'auto-disconnect',
|
|
@@ -733,17 +703,6 @@ async function transmitHexDataToDevice(
|
|
|
733
703
|
);
|
|
734
704
|
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
735
705
|
|
|
736
|
-
// Diagnostics: the frame header alone (report id + magic + message type +
|
|
737
|
-
// length) identifies which protocol frame went out, without logging payload.
|
|
738
|
-
logger?.info('[NobleBLE] Write frame', {
|
|
739
|
-
deviceId,
|
|
740
|
-
bytes: toBuffer.length,
|
|
741
|
-
head: toBuffer.subarray(0, 8).toString('hex'),
|
|
742
|
-
packetCapacity,
|
|
743
|
-
mtu: peripheral.mtu ?? null,
|
|
744
|
-
pacingDelayMs,
|
|
745
|
-
});
|
|
746
|
-
|
|
747
706
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
748
707
|
// macOS / Linux or already paired on Windows: direct write
|
|
749
708
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
@@ -802,41 +761,6 @@ async function transmitHexDataToDevice(
|
|
|
802
761
|
}
|
|
803
762
|
|
|
804
763
|
// Handle discovered device (for general enumeration only)
|
|
805
|
-
// A scan that finds nothing costs this much before the direct-connect
|
|
806
|
-
// fallback runs. Every advertisement in the 6.5.0 control logs arrived within
|
|
807
|
-
// 631ms, so this leaves ~2x headroom while keeping a miss cheap.
|
|
808
|
-
const BLE_COLD_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
809
|
-
|
|
810
|
-
// Advertised names, kept for the life of the process: device caches are purged
|
|
811
|
-
// on every disconnect, but the family a device belongs to never changes and
|
|
812
|
-
// decides which connection strategy is safe for it.
|
|
813
|
-
const bleNamesById = new Map<string, string>();
|
|
814
|
-
|
|
815
|
-
function rememberBleName(deviceId: string, name?: string | null): void {
|
|
816
|
-
const trimmed = name?.trim();
|
|
817
|
-
if (trimmed) {
|
|
818
|
-
bleNamesById.set(deviceId, trimmed);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function resolveBleName(deviceId: string): string | undefined {
|
|
823
|
-
const live =
|
|
824
|
-
connectedDevices.get(deviceId)?.advertisement?.localName?.trim() ||
|
|
825
|
-
discoveredDevices.get(deviceId)?.advertisement?.localName?.trim();
|
|
826
|
-
return live || bleNamesById.get(deviceId);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Pro2/Neo connect by id first: they are unaffected by the stale-session
|
|
831
|
-
* problem and advertise under Find My names a OneKey-filtered scan cannot
|
|
832
|
-
* match. Everything else — Classic family, and any device whose name is not
|
|
833
|
-
* known yet — scans first, because connecting by id can resolve a session the
|
|
834
|
-
* device no longer serves (see setupConnectionAndDiscoverServices).
|
|
835
|
-
*/
|
|
836
|
-
function shouldConnectByIdFirst(deviceId: string): boolean {
|
|
837
|
-
return isPro2FamilyBleName(resolveBleName(deviceId));
|
|
838
|
-
}
|
|
839
|
-
|
|
840
764
|
function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
841
765
|
// Only process OneKey candidates for general discovery. Avoid logging every
|
|
842
766
|
// ambient BLE peripheral; it makes Pro2 debugging hard to read.
|
|
@@ -846,7 +770,6 @@ function handleDeviceDiscovered(peripheral: Peripheral): void {
|
|
|
846
770
|
|
|
847
771
|
const isNewDevice = !discoveredDevices.has(peripheral.id);
|
|
848
772
|
discoveredDevices.set(peripheral.id, peripheral);
|
|
849
|
-
rememberBleName(peripheral.id, peripheral.advertisement?.localName);
|
|
850
773
|
if (isNewDevice) {
|
|
851
774
|
logger?.debug('[NobleBLE] OneKey BLE device discovered', {
|
|
852
775
|
deviceId: peripheral.id,
|
|
@@ -881,10 +804,7 @@ async function waitForNobleScanStop(nobleInstance: NobleModule): Promise<void> {
|
|
|
881
804
|
|
|
882
805
|
// Perform targeted scan for a specific device ID
|
|
883
806
|
// Uses self-contained local listener pattern - no global state needed
|
|
884
|
-
async function performTargetedScan(
|
|
885
|
-
targetDeviceId: string,
|
|
886
|
-
timeoutMs: number = NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS
|
|
887
|
-
): Promise<Peripheral | null> {
|
|
807
|
+
async function performTargetedScan(targetDeviceId: string): Promise<Peripheral | null> {
|
|
888
808
|
if (!noble) {
|
|
889
809
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
890
810
|
}
|
|
@@ -929,7 +849,7 @@ async function performTargetedScan(
|
|
|
929
849
|
const timeoutId = setTimeout(() => {
|
|
930
850
|
logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
931
851
|
finish(null).catch(reject);
|
|
932
|
-
},
|
|
852
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
933
853
|
|
|
934
854
|
// Add local listener for this scan
|
|
935
855
|
nobleInstance.on('discover', onDiscover);
|
|
@@ -1131,17 +1051,9 @@ async function discoverServicesAndCharacteristics(
|
|
|
1131
1051
|
|
|
1132
1052
|
// Main discovery logic as async function
|
|
1133
1053
|
const discoveryPromise = (async (): Promise<CharacteristicPair> => {
|
|
1134
|
-
// Step 1: Discover
|
|
1135
|
-
// narrowing: an unfiltered discovery is answered from the OS GATT cache, so
|
|
1136
|
-
// a device whose stack still advertises and accepts links but no longer
|
|
1137
|
-
// serves its application layer still looks healthy — the link comes up, the
|
|
1138
|
-
// cached services resolve, the protocol frame goes out and nothing ever
|
|
1139
|
-
// answers. A targeted query returns nothing in that state, which is what
|
|
1140
|
-
// 6.5.0 relied on to trip the recovery path below (retry, reset, fresh
|
|
1141
|
-
// scan) — the sequence that brings such a device back. Pro2/Neo expose the
|
|
1142
|
-
// same service UUID, and the selection below only ever accepts that one.
|
|
1054
|
+
// Step 1: Discover ALL services (no filter — Pro2 may use different service UUID)
|
|
1143
1055
|
const services = await new Promise<Service[]>((resolve, reject) => {
|
|
1144
|
-
peripheral.discoverServices(
|
|
1056
|
+
peripheral.discoverServices([], (error, svc) => {
|
|
1145
1057
|
if (error) {
|
|
1146
1058
|
logger?.error('[NobleBLE] Service discovery failed:', error);
|
|
1147
1059
|
reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -1152,7 +1064,7 @@ async function discoverServicesAndCharacteristics(
|
|
|
1152
1064
|
});
|
|
1153
1065
|
|
|
1154
1066
|
if (!services || services.length === 0) {
|
|
1155
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No
|
|
1067
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No services found');
|
|
1156
1068
|
}
|
|
1157
1069
|
|
|
1158
1070
|
logger?.debug('[NobleBLE] services discovered', {
|
|
@@ -1393,28 +1305,26 @@ async function setupConnectionAndDiscoverServices(
|
|
|
1393
1305
|
deviceId: string,
|
|
1394
1306
|
webContents: WebContents
|
|
1395
1307
|
): Promise<CharacteristicPair> {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
// (control log 2026-08-20: 16/16 links answered within ~1s, including after
|
|
1399
|
-
// a 15-minute idle). Demoting it to a discovery-failure fallback removed the
|
|
1400
|
-
// reset entirely for this device, because discovery always succeeds: the link
|
|
1401
|
-
// comes up, GATT resolves from cache, and the device then answers nothing on
|
|
1402
|
-
// a session it no longer serves. Costs ~1.3s per cold setup; a kept-alive
|
|
1403
|
-
// link with an intact subscription returns before reaching here, so reuse
|
|
1404
|
-
// inside a workflow is unaffected.
|
|
1308
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1309
|
+
|
|
1405
1310
|
try {
|
|
1406
|
-
await
|
|
1407
|
-
} catch (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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
|
+
});
|
|
1411
1317
|
}
|
|
1412
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1413
1318
|
|
|
1319
|
+
await forceReconnectPeripheral(peripheral, deviceId);
|
|
1320
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1414
1321
|
try {
|
|
1415
1322
|
return await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1416
|
-
} catch (
|
|
1417
|
-
logger?.error(
|
|
1323
|
+
} catch (reconnectError) {
|
|
1324
|
+
logger?.error(
|
|
1325
|
+
'[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...',
|
|
1326
|
+
reconnectError
|
|
1327
|
+
);
|
|
1418
1328
|
return freshScanAndDiscover(deviceId, webContents);
|
|
1419
1329
|
}
|
|
1420
1330
|
}
|
|
@@ -1503,7 +1413,10 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1503
1413
|
// enumerate clears the discovery map; a kept-alive link outlives it.
|
|
1504
1414
|
let peripheral = discoveredDevices.get(deviceId) ?? connectedDevices.get(deviceId);
|
|
1505
1415
|
|
|
1416
|
+
// If device not discovered, try a targeted scan for this specific device
|
|
1506
1417
|
if (!peripheral) {
|
|
1418
|
+
logger?.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
1419
|
+
|
|
1507
1420
|
// Initialize Noble if not already done
|
|
1508
1421
|
if (!noble) {
|
|
1509
1422
|
await initializeNoble();
|
|
@@ -1513,46 +1426,27 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1513
1426
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1514
1427
|
}
|
|
1515
1428
|
|
|
1516
|
-
//
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
// because they are unaffected and may advertise under an unmatchable Find
|
|
1523
|
-
// My name.
|
|
1524
|
-
const byIdFirst = shouldConnectByIdFirst(deviceId);
|
|
1525
|
-
logger?.info('[NobleBLE] Resolving device for cold connect', {
|
|
1526
|
-
deviceId,
|
|
1527
|
-
name: resolveBleName(deviceId) ?? 'unknown',
|
|
1528
|
-
strategy: byIdFirst ? 'connect-by-id-first' : 'scan-first',
|
|
1529
|
-
});
|
|
1530
|
-
|
|
1531
|
-
const scanForPeripheral = async () => {
|
|
1532
|
-
try {
|
|
1533
|
-
return (await performTargetedScan(deviceId, BLE_COLD_CONNECT_SCAN_TIMEOUT_MS)) ?? undefined;
|
|
1534
|
-
} catch (scanError) {
|
|
1535
|
-
logger?.info('[NobleBLE] Targeted scan failed', {
|
|
1536
|
-
deviceId,
|
|
1537
|
-
error: scanError instanceof Error ? scanError.message : String(scanError),
|
|
1538
|
-
});
|
|
1539
|
-
return undefined;
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
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);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1542
1435
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
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
|
+
);
|
|
1547
1445
|
}
|
|
1548
|
-
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
if (!peripheral) {
|
|
1553
|
-
// The preferred route came up empty: not advertising (held elsewhere, or
|
|
1554
|
-
// silent), or not reachable by id. Try the other one before giving up.
|
|
1555
|
-
peripheral = byIdFirst ? await scanForPeripheral() : await connectById();
|
|
1446
|
+
peripheral = foundPeripheral;
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
logger?.error('[NobleBLE] Targeted scan failed:', error);
|
|
1449
|
+
throw error;
|
|
1556
1450
|
}
|
|
1557
1451
|
}
|
|
1558
1452
|
|
|
@@ -1561,10 +1455,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1561
1455
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found`);
|
|
1562
1456
|
}
|
|
1563
1457
|
|
|
1564
|
-
// Also covers the connect-by-id route, whose simulated discovery carries no
|
|
1565
|
-
// service UUIDs and therefore never reaches handleDeviceDiscovered.
|
|
1566
|
-
rememberBleName(deviceId, peripheral.advertisement?.localName);
|
|
1567
|
-
|
|
1568
1458
|
logger?.info('[NobleBLE] Connecting to device:', deviceId);
|
|
1569
1459
|
|
|
1570
1460
|
// Check if device is already connected
|
|
@@ -1707,18 +1597,11 @@ async function disconnectDevice(deviceId: string): Promise<void> {
|
|
|
1707
1597
|
}
|
|
1708
1598
|
|
|
1709
1599
|
await runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1710
|
-
timeoutMs:
|
|
1600
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1711
1601
|
timeoutBehavior: 'resolve',
|
|
1712
1602
|
});
|
|
1713
|
-
if (peripheral.state === 'connected') {
|
|
1714
|
-
logger?.warn('[NobleBLE] Physical disconnect did not confirm in time', { deviceId });
|
|
1715
|
-
}
|
|
1716
1603
|
cleanupDevice(deviceId, undefined, {
|
|
1717
1604
|
cleanupConnection: true,
|
|
1718
|
-
// macOS returns zero GATT services when a previously-disconnected
|
|
1719
|
-
// peripheral object is reconnected; drop the discovery cache so the next
|
|
1720
|
-
// connect resolves a fresh peripheral (direct connect by id, or scan).
|
|
1721
|
-
cleanupDiscoveredCache: true,
|
|
1722
1605
|
sendDisconnectEvent: false,
|
|
1723
1606
|
cancelOperations: true,
|
|
1724
1607
|
reason: 'manual-disconnect',
|
|
@@ -1849,11 +1732,6 @@ async function subscribeNotifications(
|
|
|
1849
1732
|
});
|
|
1850
1733
|
|
|
1851
1734
|
notifyCharacteristic.on('data', (data: Buffer) => {
|
|
1852
|
-
logger?.debug('[NobleBLE] Notify frame', {
|
|
1853
|
-
deviceId,
|
|
1854
|
-
bytes: data.length,
|
|
1855
|
-
head: data.subarray(0, 8).toString('hex'),
|
|
1856
|
-
});
|
|
1857
1735
|
// Windows BLE pairing detection: receiving any data means device is paired
|
|
1858
1736
|
if (!pairedDevices.has(deviceId)) {
|
|
1859
1737
|
pairedDevices.add(deviceId);
|
|
@@ -1864,14 +1742,9 @@ async function subscribeNotifications(
|
|
|
1864
1742
|
});
|
|
1865
1743
|
}
|
|
1866
1744
|
|
|
1867
|
-
const subscribeStartedAt = Date.now();
|
|
1868
1745
|
try {
|
|
1869
1746
|
await rebuildAppSubscription(deviceId, notifyCharacteristic);
|
|
1870
1747
|
subscribedDevices.set(deviceId, true);
|
|
1871
|
-
logger?.info('[NobleBLE] Notification subscription active', {
|
|
1872
|
-
deviceId,
|
|
1873
|
-
ms: Date.now() - subscribeStartedAt,
|
|
1874
|
-
});
|
|
1875
1748
|
} finally {
|
|
1876
1749
|
// 🔒 CRITICAL: Always clear operation state (even on error)
|
|
1877
1750
|
subscriptionOperations.set(deviceId, 'idle');
|
|
@@ -2054,12 +1927,10 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
2054
1927
|
}
|
|
2055
1928
|
|
|
2056
1929
|
await stopScanning().catch(() => undefined);
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
// initializeNoble early-returns once noble is loaded). The null-guard
|
|
2062
|
-
// in setupPersistentStateListener keeps it deduped to one instance.
|
|
1930
|
+
if (noble && persistentStateListener) {
|
|
1931
|
+
noble.removeListener('stateChange', persistentStateListener);
|
|
1932
|
+
persistentStateListener = null;
|
|
1933
|
+
}
|
|
2063
1934
|
cleanupNobleListeners();
|
|
2064
1935
|
discoveredDevices.clear();
|
|
2065
1936
|
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|