@onekeyfe/hd-transport-electron 1.1.34-alpha.2 → 1.1.34-alpha.4
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-b69f3b16.js → index-de36348d.js} +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-4423b8bd.js → noble-ble-handler-2e41f27c.js} +9 -174
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/types/desktop-api.d.ts +0 -1
- package/dist/types/desktop-api.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 +26 -269
- package/src/types/desktop-api.ts +0 -2
- package/src/types/noble-extended.ts +0 -9
|
@@ -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-2e41f27c.js'); });
|
|
36
36
|
setupNobleBleHandlers(webContents);
|
|
37
37
|
});
|
|
38
38
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ interface NobleModule {
|
|
|
17
17
|
stopScanning(callback?: () => void): void;
|
|
18
18
|
on(event: 'stateChange', listener: (state: string) => void): void;
|
|
19
19
|
on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
20
|
-
connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
|
|
21
20
|
removeListener(event: 'stateChange', listener: (state: string) => void): void;
|
|
22
21
|
removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
23
22
|
}
|
|
@@ -37,7 +36,6 @@ interface NobleBleAPI {
|
|
|
37
36
|
name: string;
|
|
38
37
|
} | null>;
|
|
39
38
|
connect: (uuid: string) => Promise<void>;
|
|
40
|
-
release?: (uuid: string) => Promise<void>;
|
|
41
39
|
disconnect: (uuid: string) => Promise<void>;
|
|
42
40
|
subscribe: (uuid: string) => Promise<void>;
|
|
43
41
|
unsubscribe: (uuid: string) => Promise<void>;
|
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-de36348d.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var hdTransport = require('@onekeyfe/hd-transport');
|
|
6
6
|
var pRetry = require('p-retry');
|
|
@@ -78,10 +78,6 @@ const subscribedDevices = new Map();
|
|
|
78
78
|
const subscriptionOperations = new Map();
|
|
79
79
|
const devicePacketStates = new Map();
|
|
80
80
|
const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
|
|
81
|
-
const uuid16Key = (uuid) => {
|
|
82
|
-
const stripped = (uuid !== null && uuid !== void 0 ? uuid : '').replace(/-/g, '').toLowerCase();
|
|
83
|
-
return stripped.length >= 8 ? stripped.substring(4, 8) : stripped;
|
|
84
|
-
};
|
|
85
81
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
86
82
|
const NORMALIZED_NOTIFY_UUID = '0003';
|
|
87
83
|
const BLUETOOTH_INIT_TIMEOUT = 10000;
|
|
@@ -89,9 +85,6 @@ const DEVICE_SCAN_TIMEOUT = 5000;
|
|
|
89
85
|
const FAST_SCAN_TIMEOUT = 1500;
|
|
90
86
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
91
87
|
const CONNECTION_TIMEOUT = 3000;
|
|
92
|
-
const BLE_IDLE_DISCONNECT_MS = 60000;
|
|
93
|
-
const BLE_DISCONNECT_TIMEOUT_MS = 2000;
|
|
94
|
-
const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
|
|
95
88
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
96
89
|
const BLE_PACKET_SIZE = 192;
|
|
97
90
|
const UNIFIED_WRITE_DELAY = 5;
|
|
@@ -251,48 +244,6 @@ function updateBluetoothState(state) {
|
|
|
251
244
|
bluetoothState.initialized = true;
|
|
252
245
|
}
|
|
253
246
|
}
|
|
254
|
-
const idleDisconnectTimers = new Map();
|
|
255
|
-
function clearIdleDisconnect(deviceId) {
|
|
256
|
-
const timer = idleDisconnectTimers.get(deviceId);
|
|
257
|
-
if (timer) {
|
|
258
|
-
clearTimeout(timer);
|
|
259
|
-
idleDisconnectTimers.delete(deviceId);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
|
|
263
|
-
clearIdleDisconnect(deviceId);
|
|
264
|
-
idleDisconnectTimers.set(deviceId, setTimeout(() => {
|
|
265
|
-
var _a;
|
|
266
|
-
idleDisconnectTimers.delete(deviceId);
|
|
267
|
-
if (!connectedDevices.has(deviceId))
|
|
268
|
-
return;
|
|
269
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device:', deviceId, reason);
|
|
270
|
-
const peripheral = connectedDevices.get(deviceId);
|
|
271
|
-
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
272
|
-
disconnectDevice(deviceId)
|
|
273
|
-
.then(() => {
|
|
274
|
-
broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
|
|
275
|
-
id: deviceId,
|
|
276
|
-
name: deviceName,
|
|
277
|
-
});
|
|
278
|
-
})
|
|
279
|
-
.catch(error => {
|
|
280
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Keep-alive disconnect failed:', error);
|
|
281
|
-
});
|
|
282
|
-
}, ms));
|
|
283
|
-
}
|
|
284
|
-
function broadcastToAllWebContents(channel, payload) {
|
|
285
|
-
try {
|
|
286
|
-
const { webContents: electronWebContents } = require('electron');
|
|
287
|
-
for (const wc of electronWebContents.getAllWebContents()) {
|
|
288
|
-
if (!wc.isDestroyed())
|
|
289
|
-
wc.send(channel, payload);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
247
|
function initializeNoble() {
|
|
297
248
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
298
249
|
if (noble)
|
|
@@ -364,7 +315,6 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
364
315
|
sendDisconnectEvent,
|
|
365
316
|
cancelOperations,
|
|
366
317
|
});
|
|
367
|
-
clearIdleDisconnect(deviceId);
|
|
368
318
|
const peripheral = connectedDevices.get(deviceId);
|
|
369
319
|
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
370
320
|
if (cleanupConnection) {
|
|
@@ -507,7 +457,6 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
507
457
|
if (!peripheral || !characteristics) {
|
|
508
458
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
|
|
509
459
|
}
|
|
510
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
511
460
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
512
461
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Writing data:', {
|
|
513
462
|
deviceId,
|
|
@@ -757,7 +706,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
757
706
|
});
|
|
758
707
|
const discoveryPromise = (() => index.__awaiter(this, void 0, void 0, function* () {
|
|
759
708
|
const services = yield new Promise((resolve, reject) => {
|
|
760
|
-
peripheral.discoverServices(
|
|
709
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
761
710
|
if (error) {
|
|
762
711
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed:', error);
|
|
763
712
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -770,14 +719,10 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
770
719
|
if (!services || services.length === 0) {
|
|
771
720
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
772
721
|
}
|
|
773
|
-
const
|
|
774
|
-
const service = services.find(svc => wanted.includes(uuid16Key(svc.uuid)));
|
|
775
|
-
if (!service) {
|
|
776
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound, `No OneKey service in result set: ${services.map(svc => svc.uuid).join('|')}`);
|
|
777
|
-
}
|
|
722
|
+
const service = services[0];
|
|
778
723
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Found service:', service.uuid);
|
|
779
724
|
const characteristics = yield new Promise((resolve, reject) => {
|
|
780
|
-
service.discoverCharacteristics([], (error, chars) => {
|
|
725
|
+
service.discoverCharacteristics([hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID, hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID], (error, chars) => {
|
|
781
726
|
if (error) {
|
|
782
727
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
783
728
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
@@ -856,21 +801,8 @@ function forceReconnectPeripheral(peripheral, deviceId) {
|
|
|
856
801
|
});
|
|
857
802
|
}
|
|
858
803
|
function freshScanAndDiscover(deviceId, webContents) {
|
|
859
|
-
var _a;
|
|
860
804
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
861
805
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Performing fresh scan to get new peripheral object for device:', deviceId);
|
|
862
|
-
const stalePeripheral = (_a = connectedDevices.get(deviceId)) !== null && _a !== void 0 ? _a : discoveredDevices.get(deviceId);
|
|
863
|
-
if (stalePeripheral && stalePeripheral.state === 'connected') {
|
|
864
|
-
stalePeripheral.removeAllListeners('disconnect');
|
|
865
|
-
yield new Promise(resolve => {
|
|
866
|
-
const timer = setTimeout(resolve, BLE_DISCONNECT_TIMEOUT_MS);
|
|
867
|
-
stalePeripheral.disconnect(() => {
|
|
868
|
-
clearTimeout(timer);
|
|
869
|
-
resolve();
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
connectedDevices.delete(deviceId);
|
|
873
|
-
}
|
|
874
806
|
const freshPeripheral = yield performTargetedScan(deviceId);
|
|
875
807
|
if (!freshPeripheral) {
|
|
876
808
|
discoveredDevices.delete(deviceId);
|
|
@@ -895,13 +827,7 @@ function freshScanAndDiscover(deviceId, webContents) {
|
|
|
895
827
|
setupDisconnectListener(freshPeripheral, deviceId, webContents);
|
|
896
828
|
yield hdShared.wait(500);
|
|
897
829
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Attempting service discovery with fresh peripheral');
|
|
898
|
-
|
|
899
|
-
return yield discoverServicesAndCharacteristics(freshPeripheral);
|
|
900
|
-
}
|
|
901
|
-
catch (error) {
|
|
902
|
-
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
903
|
-
throw error;
|
|
904
|
-
}
|
|
830
|
+
return discoverServicesAndCharacteristics(freshPeripheral);
|
|
905
831
|
});
|
|
906
832
|
}
|
|
907
833
|
function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
@@ -947,22 +873,10 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
947
873
|
}
|
|
948
874
|
function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
949
875
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
876
|
+
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
950
877
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
951
|
-
if (peripheral.state === 'connected') {
|
|
952
|
-
try {
|
|
953
|
-
const result = yield discoverServicesAndCharacteristics(peripheral);
|
|
954
|
-
connectedDevices.set(deviceId, peripheral);
|
|
955
|
-
return result;
|
|
956
|
-
}
|
|
957
|
-
catch (directError) {
|
|
958
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct discovery miss, escalating:', String(directError));
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
878
|
try {
|
|
962
|
-
yield
|
|
963
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
964
|
-
const result = yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
965
|
-
return result;
|
|
879
|
+
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
966
880
|
}
|
|
967
881
|
catch (error) {
|
|
968
882
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...', error);
|
|
@@ -970,51 +884,6 @@ function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
|
970
884
|
}
|
|
971
885
|
});
|
|
972
886
|
}
|
|
973
|
-
const DIRECT_CONNECT_TIMEOUT_MS = 2000;
|
|
974
|
-
const DIRECT_CONNECT_COOLDOWN_MS = 15000;
|
|
975
|
-
const directConnectCooldownUntil = new Map();
|
|
976
|
-
function tryDirectConnectById(deviceId) {
|
|
977
|
-
var _a;
|
|
978
|
-
return index.__awaiter(this, void 0, void 0, function* () {
|
|
979
|
-
if (!noble || typeof noble.connectAsync !== 'function')
|
|
980
|
-
return undefined;
|
|
981
|
-
if (((_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0) > Date.now()) {
|
|
982
|
-
return undefined;
|
|
983
|
-
}
|
|
984
|
-
try {
|
|
985
|
-
const directPromise = noble.connectAsync(deviceId);
|
|
986
|
-
const raced = yield Promise.race([
|
|
987
|
-
directPromise,
|
|
988
|
-
new Promise(resolve => {
|
|
989
|
-
setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
|
|
990
|
-
}),
|
|
991
|
-
]);
|
|
992
|
-
if (raced === 'timeout') {
|
|
993
|
-
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
994
|
-
directPromise
|
|
995
|
-
.then(late => {
|
|
996
|
-
const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
|
|
997
|
-
if (latePeripheral &&
|
|
998
|
-
latePeripheral.state === 'connected' &&
|
|
999
|
-
!connectedDevices.has(deviceId)) {
|
|
1000
|
-
latePeripheral.removeAllListeners('disconnect');
|
|
1001
|
-
latePeripheral.disconnect(() => { });
|
|
1002
|
-
}
|
|
1003
|
-
})
|
|
1004
|
-
.catch(() => { });
|
|
1005
|
-
return undefined;
|
|
1006
|
-
}
|
|
1007
|
-
const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
|
|
1008
|
-
if (!peripheral || peripheral.state !== 'connected')
|
|
1009
|
-
return undefined;
|
|
1010
|
-
discoveredDevices.set(deviceId, peripheral);
|
|
1011
|
-
return peripheral;
|
|
1012
|
-
}
|
|
1013
|
-
catch (error) {
|
|
1014
|
-
return undefined;
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
887
|
function connectDevice(deviceId, webContents) {
|
|
1019
888
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1020
889
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
@@ -1034,9 +903,8 @@ function connectDevice(deviceId, webContents) {
|
|
|
1034
903
|
if (!noble) {
|
|
1035
904
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1036
905
|
}
|
|
1037
|
-
peripheral = yield tryDirectConnectById(deviceId);
|
|
1038
906
|
try {
|
|
1039
|
-
const foundPeripheral =
|
|
907
|
+
const foundPeripheral = yield performTargetedScan(deviceId);
|
|
1040
908
|
if (!foundPeripheral) {
|
|
1041
909
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${deviceId} not found even after targeted scan`);
|
|
1042
910
|
}
|
|
@@ -1085,21 +953,12 @@ function connectDevice(deviceId, webContents) {
|
|
|
1085
953
|
return;
|
|
1086
954
|
}
|
|
1087
955
|
return new Promise((resolve, reject) => {
|
|
1088
|
-
let timedOut = false;
|
|
1089
956
|
const timeout = setTimeout(() => {
|
|
1090
|
-
timedOut = true;
|
|
1091
957
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
|
|
1092
958
|
}, CONNECTION_TIMEOUT);
|
|
1093
959
|
const connectedPeripheral = peripheral;
|
|
1094
960
|
connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1095
961
|
clearTimeout(timeout);
|
|
1096
|
-
if (timedOut) {
|
|
1097
|
-
if (!error && connectedPeripheral.state === 'connected') {
|
|
1098
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Late connection after timeout, disconnecting:', deviceId);
|
|
1099
|
-
connectedPeripheral.disconnect(() => { });
|
|
1100
|
-
}
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
962
|
if (error) {
|
|
1104
963
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
|
|
1105
964
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
|
|
@@ -1131,11 +990,7 @@ function disconnectDevice(deviceId) {
|
|
|
1131
990
|
}
|
|
1132
991
|
return new Promise(resolve => {
|
|
1133
992
|
peripheral.removeAllListeners('disconnect');
|
|
1134
|
-
|
|
1135
|
-
const finish = () => {
|
|
1136
|
-
if (settled)
|
|
1137
|
-
return;
|
|
1138
|
-
settled = true;
|
|
993
|
+
peripheral.disconnect(() => {
|
|
1139
994
|
cleanupDevice(deviceId, undefined, {
|
|
1140
995
|
cleanupConnection: true,
|
|
1141
996
|
sendDisconnectEvent: false,
|
|
@@ -1143,11 +998,6 @@ function disconnectDevice(deviceId) {
|
|
|
1143
998
|
reason: 'manual-disconnect',
|
|
1144
999
|
});
|
|
1145
1000
|
resolve();
|
|
1146
|
-
};
|
|
1147
|
-
const timer = setTimeout(finish, BLE_DISCONNECT_TIMEOUT_MS);
|
|
1148
|
-
peripheral.disconnect(() => {
|
|
1149
|
-
clearTimeout(timer);
|
|
1150
|
-
finish();
|
|
1151
1001
|
});
|
|
1152
1002
|
});
|
|
1153
1003
|
});
|
|
@@ -1312,17 +1162,11 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1312
1162
|
hasCharacteristics: deviceCharacteristics.has(deviceId),
|
|
1313
1163
|
totalConnectedDevices: connectedDevices.size,
|
|
1314
1164
|
});
|
|
1315
|
-
clearIdleDisconnect(deviceId);
|
|
1316
1165
|
yield connectDevice(deviceId, webContents);
|
|
1317
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1318
1166
|
}));
|
|
1319
1167
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1320
1168
|
yield disconnectDevice(deviceId);
|
|
1321
1169
|
}));
|
|
1322
|
-
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE, (_event, deviceId) => {
|
|
1323
|
-
if (connectedDevices.has(deviceId))
|
|
1324
|
-
armIdleDisconnect(deviceId);
|
|
1325
|
-
});
|
|
1326
1170
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1327
1171
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
|
|
1328
1172
|
yield transmitHexDataToDevice(deviceId, hexData);
|
|
@@ -1331,11 +1175,9 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1331
1175
|
yield subscribeNotifications(deviceId, (data) => {
|
|
1332
1176
|
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
|
|
1333
1177
|
});
|
|
1334
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1335
1178
|
}));
|
|
1336
1179
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1337
1180
|
yield unsubscribeNotifications(deviceId);
|
|
1338
|
-
armIdleDisconnect(deviceId);
|
|
1339
1181
|
}));
|
|
1340
1182
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1341
1183
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
@@ -1373,19 +1215,12 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1373
1215
|
safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
|
|
1374
1216
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
1375
1217
|
deviceIds.forEach(deviceId => {
|
|
1376
|
-
const peripheral = connectedDevices.get(deviceId);
|
|
1377
1218
|
cleanupDevice(deviceId, undefined, {
|
|
1378
1219
|
cleanupConnection: true,
|
|
1379
1220
|
sendDisconnectEvent: false,
|
|
1380
1221
|
cancelOperations: true,
|
|
1381
1222
|
reason: 'app-quit',
|
|
1382
1223
|
});
|
|
1383
|
-
if (peripheral && peripheral.state === 'connected') {
|
|
1384
|
-
peripheral.removeAllListeners('disconnect');
|
|
1385
|
-
peripheral.disconnect(() => {
|
|
1386
|
-
safeLog(logger, 'info', `Disconnected ${deviceId} on window teardown`);
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
1224
|
});
|
|
1390
1225
|
stopScanning();
|
|
1391
1226
|
if (noble && persistentStateListener) {
|
|
@@ -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":"AAwBA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAshDhE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAsKpE"}
|
|
@@ -8,7 +8,6 @@ export interface NobleBleAPI {
|
|
|
8
8
|
name: string;
|
|
9
9
|
} | null>;
|
|
10
10
|
connect: (uuid: string) => Promise<void>;
|
|
11
|
-
release?: (uuid: string) => Promise<void>;
|
|
12
11
|
disconnect: (uuid: string) => Promise<void>;
|
|
13
12
|
subscribe: (uuid: string) => Promise<void>;
|
|
14
13
|
unsubscribe: (uuid: string) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,oBAAoB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/F,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
|
|
@@ -15,7 +15,6 @@ export interface NobleModule {
|
|
|
15
15
|
stopScanning(callback?: () => void): void;
|
|
16
16
|
on(event: 'stateChange', listener: (state: string) => void): void;
|
|
17
17
|
on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
18
|
-
connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
|
|
19
18
|
removeListener(event: 'stateChange', listener: (state: string) => void): void;
|
|
20
19
|
removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
21
20
|
}
|
|
@@ -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;CACf;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;
|
|
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;CACf;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.1.34-alpha.
|
|
3
|
+
"version": "1.1.34-alpha.4",
|
|
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.1.34-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.1.34-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.1.34-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.1.34-alpha.4",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.1.34-alpha.4",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.1.34-alpha.4",
|
|
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": "d546bc55410990351d34009230aefea77725d0d6"
|
|
40
40
|
}
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
EOneKeyBleMessageKeys,
|
|
10
10
|
ERRORS,
|
|
11
11
|
HardwareErrorCode,
|
|
12
|
+
ONEKEY_NOTIFY_CHARACTERISTIC_UUID,
|
|
12
13
|
ONEKEY_SERVICE_UUID,
|
|
14
|
+
ONEKEY_WRITE_CHARACTERISTIC_UUID,
|
|
13
15
|
isHeaderChunk,
|
|
14
16
|
isOnekeyDevice,
|
|
15
17
|
wait,
|
|
@@ -72,13 +74,6 @@ const devicePacketStates = new Map<string, PacketAssemblyState>();
|
|
|
72
74
|
const ONEKEY_SERVICE_UUIDS = [ONEKEY_SERVICE_UUID];
|
|
73
75
|
|
|
74
76
|
// Pre-normalized characteristic identifiers for fast comparison
|
|
75
|
-
// Reduce any uuid form (long base-form with/without dashes, or short) to its
|
|
76
|
-
// 16-bit key for comparisons — noble/mac reports base-UUIDs short-form.
|
|
77
|
-
const uuid16Key = (uuid: string): string => {
|
|
78
|
-
const stripped = (uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
79
|
-
return stripped.length >= 8 ? stripped.substring(4, 8) : stripped;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
77
|
const NORMALIZED_WRITE_UUID = '0002';
|
|
83
78
|
const NORMALIZED_NOTIFY_UUID = '0003';
|
|
84
79
|
|
|
@@ -88,15 +83,6 @@ const DEVICE_SCAN_TIMEOUT = 5000; // 5 seconds for device scanning
|
|
|
88
83
|
const FAST_SCAN_TIMEOUT = 1500; // 1.5 seconds for fast targeted scanning
|
|
89
84
|
const DEVICE_CHECK_INTERVAL = 500; // 500ms interval for periodic device checks
|
|
90
85
|
const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
|
|
91
|
-
// Idle window before a kept-alive link is dropped so other hosts (phone) can
|
|
92
|
-
// connect; matches the device's 60s auto-lock default.
|
|
93
|
-
const BLE_IDLE_DISCONNECT_MS = 60_000;
|
|
94
|
-
// noble's disconnect callback can hang on a wedged peripheral — bound it.
|
|
95
|
-
const BLE_DISCONNECT_TIMEOUT_MS = 2000;
|
|
96
|
-
// Backstop while a request is outstanding: the idle clock must not run while
|
|
97
|
-
// the user is on the device's confirm screen, but a dead call must not hold
|
|
98
|
-
// the link forever either.
|
|
99
|
-
const BLE_BUSY_BACKSTOP_MS = 10 * 60_000;
|
|
100
86
|
const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
|
|
101
87
|
|
|
102
88
|
// Write-related constants
|
|
@@ -316,64 +302,6 @@ function updateBluetoothState(state: string): void {
|
|
|
316
302
|
}
|
|
317
303
|
}
|
|
318
304
|
|
|
319
|
-
// ===== Keep-alive idle disconnect =====
|
|
320
|
-
// One timer per device: a write swaps it for the busy backstop, a complete
|
|
321
|
-
// response re-arms the 60s idle clock. Lives in the main process so a renderer
|
|
322
|
-
// reload cannot orphan a held link.
|
|
323
|
-
const idleDisconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
324
|
-
|
|
325
|
-
function clearIdleDisconnect(deviceId: string): void {
|
|
326
|
-
const timer = idleDisconnectTimers.get(deviceId);
|
|
327
|
-
if (timer) {
|
|
328
|
-
clearTimeout(timer);
|
|
329
|
-
idleDisconnectTimers.delete(deviceId);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function armIdleDisconnect(
|
|
334
|
-
deviceId: string,
|
|
335
|
-
ms: number = BLE_IDLE_DISCONNECT_MS,
|
|
336
|
-
reason: 'idle' | 'busy-backstop' = 'idle'
|
|
337
|
-
): void {
|
|
338
|
-
clearIdleDisconnect(deviceId);
|
|
339
|
-
idleDisconnectTimers.set(
|
|
340
|
-
deviceId,
|
|
341
|
-
setTimeout(() => {
|
|
342
|
-
idleDisconnectTimers.delete(deviceId);
|
|
343
|
-
if (!connectedDevices.has(deviceId)) return;
|
|
344
|
-
logger?.info('[NobleBLE] Keep-alive timeout, disconnecting device:', deviceId, reason);
|
|
345
|
-
const peripheral = connectedDevices.get(deviceId);
|
|
346
|
-
const deviceName = peripheral?.advertisement?.localName || 'Unknown Device';
|
|
347
|
-
disconnectDevice(deviceId)
|
|
348
|
-
.then(() => {
|
|
349
|
-
// Notify renderers: on the busy backstop a call is still in flight
|
|
350
|
-
// and must reject instead of hanging on a dead link.
|
|
351
|
-
broadcastToAllWebContents(EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
|
|
352
|
-
id: deviceId,
|
|
353
|
-
name: deviceName,
|
|
354
|
-
});
|
|
355
|
-
})
|
|
356
|
-
.catch(error => {
|
|
357
|
-
logger?.error('[NobleBLE] Keep-alive disconnect failed:', error);
|
|
358
|
-
});
|
|
359
|
-
}, ms)
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function broadcastToAllWebContents(channel: string, payload: unknown): void {
|
|
364
|
-
try {
|
|
365
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
366
|
-
const { webContents: electronWebContents } = require('electron') as {
|
|
367
|
-
webContents: { getAllWebContents(): WebContents[] };
|
|
368
|
-
};
|
|
369
|
-
for (const wc of electronWebContents.getAllWebContents()) {
|
|
370
|
-
if (!wc.isDestroyed()) wc.send(channel, payload);
|
|
371
|
-
}
|
|
372
|
-
} catch (error) {
|
|
373
|
-
logger?.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
305
|
// Initialize Noble
|
|
378
306
|
async function initializeNoble(): Promise<void> {
|
|
379
307
|
if (noble) return;
|
|
@@ -490,7 +418,6 @@ function cleanupDevice(
|
|
|
490
418
|
sendDisconnectEvent,
|
|
491
419
|
cancelOperations,
|
|
492
420
|
});
|
|
493
|
-
clearIdleDisconnect(deviceId);
|
|
494
421
|
|
|
495
422
|
// Get device info before cleanup
|
|
496
423
|
const peripheral = connectedDevices.get(deviceId);
|
|
@@ -689,9 +616,6 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
689
616
|
`Device ${deviceId} not connected or characteristics not available`
|
|
690
617
|
);
|
|
691
618
|
}
|
|
692
|
-
// Request outstanding: swap the idle clock for the busy backstop; the
|
|
693
|
-
// complete-response handler re-arms the 60s idle clock.
|
|
694
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
695
619
|
|
|
696
620
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
697
621
|
logger?.info('[NobleBLE] Writing data:', {
|
|
@@ -1028,13 +952,9 @@ async function discoverServicesAndCharacteristics(
|
|
|
1028
952
|
|
|
1029
953
|
// Main discovery logic as async function
|
|
1030
954
|
const discoveryPromise = (async (): Promise<CharacteristicPair> => {
|
|
1031
|
-
// Step 1: Discover services (promisified)
|
|
1032
|
-
// everything and pick the OneKey service in JS — field data (Classic,
|
|
1033
|
-
// 2026-07-19) showed the UUID-filtered query returning empty on a link
|
|
1034
|
-
// where an unfiltered probe saw 5 services, so the filter itself is under
|
|
1035
|
-
// suspicion (same failure family as the Windows scan-filter issue).
|
|
955
|
+
// Step 1: Discover services (promisified)
|
|
1036
956
|
const services = await new Promise<Service[]>((resolve, reject) => {
|
|
1037
|
-
peripheral.discoverServices(
|
|
957
|
+
peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error, svc) => {
|
|
1038
958
|
if (error) {
|
|
1039
959
|
logger?.error('[NobleBLE] Service discovery failed:', error);
|
|
1040
960
|
reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
|
|
@@ -1048,28 +968,22 @@ async function discoverServicesAndCharacteristics(
|
|
|
1048
968
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, 'No OneKey services found');
|
|
1049
969
|
}
|
|
1050
970
|
|
|
1051
|
-
|
|
1052
|
-
// base-UUID services short-form ('0001').
|
|
1053
|
-
const wanted = ONEKEY_SERVICE_UUIDS.map(uuid16Key);
|
|
1054
|
-
const service = services.find(svc => wanted.includes(uuid16Key(svc.uuid)));
|
|
1055
|
-
if (!service) {
|
|
1056
|
-
// Carry the uuid list so the trace shows what the device exposed.
|
|
1057
|
-
throw ERRORS.TypedError(
|
|
1058
|
-
HardwareErrorCode.BleServiceNotFound,
|
|
1059
|
-
`No OneKey service in result set: ${services.map(svc => svc.uuid).join('|')}`
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
971
|
+
const service = services[0];
|
|
1062
972
|
logger?.info('[NobleBLE] Found service:', service.uuid);
|
|
1063
973
|
|
|
974
|
+
// Step 2: Discover characteristics (promisified)
|
|
1064
975
|
const characteristics = await new Promise<Characteristic[]>((resolve, reject) => {
|
|
1065
|
-
service.discoverCharacteristics(
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
976
|
+
service.discoverCharacteristics(
|
|
977
|
+
[ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
|
|
978
|
+
(error, chars) => {
|
|
979
|
+
if (error) {
|
|
980
|
+
logger?.error('[NobleBLE] Characteristic discovery failed:', error);
|
|
981
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
|
|
982
|
+
} else {
|
|
983
|
+
resolve(chars);
|
|
984
|
+
}
|
|
1071
985
|
}
|
|
1072
|
-
|
|
986
|
+
);
|
|
1073
987
|
});
|
|
1074
988
|
|
|
1075
989
|
// Step 3: Find required characteristics
|
|
@@ -1172,6 +1086,7 @@ async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string
|
|
|
1172
1086
|
// NOTE: Caller MUST call setupDisconnectListener() after this function returns
|
|
1173
1087
|
}
|
|
1174
1088
|
|
|
1089
|
+
// Last resort: Fresh scan to get completely new peripheral object and discover services
|
|
1175
1090
|
async function freshScanAndDiscover(
|
|
1176
1091
|
deviceId: string,
|
|
1177
1092
|
webContents: WebContents
|
|
@@ -1181,21 +1096,6 @@ async function freshScanAndDiscover(
|
|
|
1181
1096
|
deviceId
|
|
1182
1097
|
);
|
|
1183
1098
|
|
|
1184
|
-
// The device does not advertise while we hold a link — drop the old one
|
|
1185
|
-
// first (bounded) so it goes back on air before we scan.
|
|
1186
|
-
const stalePeripheral = connectedDevices.get(deviceId) ?? discoveredDevices.get(deviceId);
|
|
1187
|
-
if (stalePeripheral && stalePeripheral.state === 'connected') {
|
|
1188
|
-
stalePeripheral.removeAllListeners('disconnect');
|
|
1189
|
-
await new Promise<void>(resolve => {
|
|
1190
|
-
const timer = setTimeout(resolve, BLE_DISCONNECT_TIMEOUT_MS);
|
|
1191
|
-
stalePeripheral.disconnect(() => {
|
|
1192
|
-
clearTimeout(timer);
|
|
1193
|
-
resolve();
|
|
1194
|
-
});
|
|
1195
|
-
});
|
|
1196
|
-
connectedDevices.delete(deviceId);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1099
|
const freshPeripheral = await performTargetedScan(deviceId);
|
|
1200
1100
|
if (!freshPeripheral) {
|
|
1201
1101
|
// Deep cleanup: fresh scan found no device, reset to initial state
|
|
@@ -1236,17 +1136,9 @@ async function freshScanAndDiscover(
|
|
|
1236
1136
|
// Wait for connection to stabilize (fresh peripheral doesn't need GATT cache clearing)
|
|
1237
1137
|
await wait(500);
|
|
1238
1138
|
|
|
1239
|
-
// Attempt service discovery with fresh peripheral
|
|
1240
|
-
// peripheral is already physically connected and registered in
|
|
1241
|
-
// connectedDevices with no idle timer armed — keep-alive release() is
|
|
1242
|
-
// logical-only and would never drop it — so tear it down before rethrowing.
|
|
1139
|
+
// Attempt service discovery with fresh peripheral
|
|
1243
1140
|
logger?.info('[NobleBLE] Attempting service discovery with fresh peripheral');
|
|
1244
|
-
|
|
1245
|
-
return await discoverServicesAndCharacteristics(freshPeripheral);
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
await disconnectDevice(deviceId).catch(() => undefined);
|
|
1248
|
-
throw error;
|
|
1249
|
-
}
|
|
1141
|
+
return discoverServicesAndCharacteristics(freshPeripheral);
|
|
1250
1142
|
}
|
|
1251
1143
|
|
|
1252
1144
|
// Enhanced service discovery with p-retry for robust BLE connection
|
|
@@ -1317,94 +1209,20 @@ async function setupConnectionAndDiscoverServices(
|
|
|
1317
1209
|
deviceId: string,
|
|
1318
1210
|
webContents: WebContents
|
|
1319
1211
|
): Promise<CharacteristicPair> {
|
|
1212
|
+
// Force reconnect to clear GATT cache
|
|
1213
|
+
await forceReconnectPeripheral(peripheral, deviceId);
|
|
1320
1214
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1321
1215
|
|
|
1322
|
-
//
|
|
1323
|
-
// runs if it fails. Discovery is UNFILTERED with 16-bit-key matching in JS:
|
|
1324
|
-
// the device answers only the 2-byte-encoded targeted query, so a uuid-
|
|
1325
|
-
// filtered discovery is unreliable (macOS CoreBluetooth sends the 128-bit
|
|
1326
|
-
// base form and misses). See uuid16Key.
|
|
1327
|
-
if (peripheral.state === 'connected') {
|
|
1328
|
-
try {
|
|
1329
|
-
const result = await discoverServicesAndCharacteristics(peripheral);
|
|
1330
|
-
connectedDevices.set(deviceId, peripheral);
|
|
1331
|
-
return result;
|
|
1332
|
-
} catch (directError) {
|
|
1333
|
-
logger?.info('[NobleBLE] Direct discovery miss, escalating:', String(directError));
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Fallback ladder; a force-reconnect failure must also escalate to the
|
|
1338
|
-
// fresh scan instead of propagating out.
|
|
1216
|
+
// Discover services and characteristics with retry
|
|
1339
1217
|
try {
|
|
1340
|
-
await
|
|
1341
|
-
// forceReconnectPeripheral strips listeners — re-attach.
|
|
1342
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
1343
|
-
|
|
1344
|
-
const result = await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1345
|
-
return result;
|
|
1218
|
+
return await discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
1346
1219
|
} catch (error) {
|
|
1347
1220
|
// Last resort: fresh scan to get new peripheral object
|
|
1348
1221
|
logger?.error('[NobleBLE] Service discovery failed, attempting fresh scan...', error);
|
|
1349
|
-
// Broken-OS-bond signature lands here.
|
|
1350
1222
|
return freshScanAndDiscover(deviceId, webContents);
|
|
1351
1223
|
}
|
|
1352
1224
|
}
|
|
1353
1225
|
|
|
1354
|
-
// noble/mac never resolves connect-by-id for an unretrievable peripheral —
|
|
1355
|
-
// time-box it, and after a timeout skip direct for a while so the retry
|
|
1356
|
-
// ladder doesn't re-pay the 2s against an absent device.
|
|
1357
|
-
const DIRECT_CONNECT_TIMEOUT_MS = 2000;
|
|
1358
|
-
const DIRECT_CONNECT_COOLDOWN_MS = 15_000;
|
|
1359
|
-
const directConnectCooldownUntil = new Map<string, number>();
|
|
1360
|
-
|
|
1361
|
-
/**
|
|
1362
|
-
* Bounded connect-by-id with no scan; undefined -> caller falls back to the
|
|
1363
|
-
* targeted scan. A late success after the timeout is dropped unless claimed.
|
|
1364
|
-
*/
|
|
1365
|
-
async function tryDirectConnectById(deviceId: string): Promise<Peripheral | undefined> {
|
|
1366
|
-
if (!noble || typeof noble.connectAsync !== 'function') return undefined;
|
|
1367
|
-
if ((directConnectCooldownUntil.get(deviceId) ?? 0) > Date.now()) {
|
|
1368
|
-
return undefined;
|
|
1369
|
-
}
|
|
1370
|
-
try {
|
|
1371
|
-
// The late-orphan guard must attach to THIS pending connect.
|
|
1372
|
-
const directPromise = noble.connectAsync(deviceId);
|
|
1373
|
-
const raced = await Promise.race([
|
|
1374
|
-
directPromise,
|
|
1375
|
-
new Promise<'timeout'>(resolve => {
|
|
1376
|
-
setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
|
|
1377
|
-
}),
|
|
1378
|
-
]);
|
|
1379
|
-
if (raced === 'timeout') {
|
|
1380
|
-
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1381
|
-
// If the pending connect completes later, drop it unless someone claimed it.
|
|
1382
|
-
directPromise
|
|
1383
|
-
.then(late => {
|
|
1384
|
-
const latePeripheral = late ?? discoveredDevices.get(deviceId);
|
|
1385
|
-
if (
|
|
1386
|
-
latePeripheral &&
|
|
1387
|
-
latePeripheral.state === 'connected' &&
|
|
1388
|
-
!connectedDevices.has(deviceId)
|
|
1389
|
-
) {
|
|
1390
|
-
latePeripheral.removeAllListeners('disconnect');
|
|
1391
|
-
latePeripheral.disconnect(() => {});
|
|
1392
|
-
}
|
|
1393
|
-
})
|
|
1394
|
-
.catch(() => {});
|
|
1395
|
-
return undefined;
|
|
1396
|
-
}
|
|
1397
|
-
// Backends emit a `discover` for the peripheral as a side effect, so the
|
|
1398
|
-
// cache may hold it even when connectAsync resolves without a value.
|
|
1399
|
-
const peripheral = raced ?? discoveredDevices.get(deviceId);
|
|
1400
|
-
if (!peripheral || peripheral.state !== 'connected') return undefined;
|
|
1401
|
-
discoveredDevices.set(deviceId, peripheral);
|
|
1402
|
-
return peripheral;
|
|
1403
|
-
} catch (error) {
|
|
1404
|
-
return undefined;
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
1226
|
// Connect to device - supports both discovered and direct connection modes
|
|
1409
1227
|
async function connectDevice(deviceId: string, webContents: WebContents): Promise<void> {
|
|
1410
1228
|
logger?.info('[NobleBLE] Connect device request:', {
|
|
@@ -1431,13 +1249,9 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1431
1249
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
1432
1250
|
}
|
|
1433
1251
|
|
|
1434
|
-
// Bounded connect-by-id first: skips the ~650ms targeted scan and reaches
|
|
1435
|
-
// a device that is not advertising; falls through to the scan on timeout.
|
|
1436
|
-
peripheral = await tryDirectConnectById(deviceId);
|
|
1437
|
-
|
|
1438
1252
|
// Perform a targeted scan to find the specific device
|
|
1439
1253
|
try {
|
|
1440
|
-
const foundPeripheral =
|
|
1254
|
+
const foundPeripheral = await performTargetedScan(deviceId);
|
|
1441
1255
|
if (!foundPeripheral) {
|
|
1442
1256
|
throw ERRORS.TypedError(
|
|
1443
1257
|
HardwareErrorCode.DeviceNotFound,
|
|
@@ -1523,11 +1337,7 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1523
1337
|
}
|
|
1524
1338
|
|
|
1525
1339
|
return new Promise((resolve, reject) => {
|
|
1526
|
-
// After the timeout rejects, a late successful connect would be an
|
|
1527
|
-
// ownerless link with no idle timer — detect and drop it.
|
|
1528
|
-
let timedOut = false;
|
|
1529
1340
|
const timeout = setTimeout(() => {
|
|
1530
|
-
timedOut = true;
|
|
1531
1341
|
reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
|
|
1532
1342
|
}, CONNECTION_TIMEOUT);
|
|
1533
1343
|
|
|
@@ -1536,14 +1346,6 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
|
|
|
1536
1346
|
connectedPeripheral.connect(async (error: Error | undefined) => {
|
|
1537
1347
|
clearTimeout(timeout);
|
|
1538
1348
|
|
|
1539
|
-
if (timedOut) {
|
|
1540
|
-
if (!error && connectedPeripheral.state === 'connected') {
|
|
1541
|
-
logger?.info('[NobleBLE] Late connection after timeout, disconnecting:', deviceId);
|
|
1542
|
-
connectedPeripheral.disconnect(() => {});
|
|
1543
|
-
}
|
|
1544
|
-
return;
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
1349
|
if (error) {
|
|
1548
1350
|
logger?.error('[NobleBLE] Connection failed:', error);
|
|
1549
1351
|
reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
|
|
@@ -1584,13 +1386,7 @@ async function disconnectDevice(deviceId: string): Promise<void> {
|
|
|
1584
1386
|
// Remove disconnect listener to avoid triggering handleDeviceDisconnect
|
|
1585
1387
|
peripheral.removeAllListeners('disconnect');
|
|
1586
1388
|
|
|
1587
|
-
|
|
1588
|
-
// invoke the callback, and callers (e.g. the idle/backstop timer) rely on
|
|
1589
|
-
// this resolving so cleanup + the renderer-facing disconnect broadcast run.
|
|
1590
|
-
let settled = false;
|
|
1591
|
-
const finish = () => {
|
|
1592
|
-
if (settled) return;
|
|
1593
|
-
settled = true;
|
|
1389
|
+
peripheral.disconnect(() => {
|
|
1594
1390
|
// Clean up device state using unified function
|
|
1595
1391
|
cleanupDevice(deviceId, undefined, {
|
|
1596
1392
|
cleanupConnection: true,
|
|
@@ -1599,11 +1395,6 @@ async function disconnectDevice(deviceId: string): Promise<void> {
|
|
|
1599
1395
|
reason: 'manual-disconnect',
|
|
1600
1396
|
});
|
|
1601
1397
|
resolve();
|
|
1602
|
-
};
|
|
1603
|
-
const timer = setTimeout(finish, BLE_DISCONNECT_TIMEOUT_MS);
|
|
1604
|
-
peripheral.disconnect(() => {
|
|
1605
|
-
clearTimeout(timer);
|
|
1606
|
-
finish();
|
|
1607
1398
|
});
|
|
1608
1399
|
});
|
|
1609
1400
|
}
|
|
@@ -1773,11 +1564,6 @@ async function subscribeNotifications(
|
|
|
1773
1564
|
return;
|
|
1774
1565
|
}
|
|
1775
1566
|
if (result.isComplete && result.completePacket) {
|
|
1776
|
-
// A complete response does NOT mean the operation is over — the run
|
|
1777
|
-
// may continue (further request/response, or an on-device prompt whose
|
|
1778
|
-
// Ack the host writes after slow human input). Do NOT re-arm the 60s
|
|
1779
|
-
// idle clock here; that starts only on logical release. The busy
|
|
1780
|
-
// backstop remains the in-flight ceiling.
|
|
1781
1567
|
const appCb = notificationCallbacks.get(deviceId);
|
|
1782
1568
|
if (appCb) appCb(result.completePacket);
|
|
1783
1569
|
}
|
|
@@ -1846,15 +1632,7 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1846
1632
|
hasCharacteristics: deviceCharacteristics.has(deviceId),
|
|
1847
1633
|
totalConnectedDevices: connectedDevices.size,
|
|
1848
1634
|
});
|
|
1849
|
-
// An armed idle timer must not fire mid-connect (pairing can take ~30s).
|
|
1850
|
-
clearIdleDisconnect(deviceId);
|
|
1851
1635
|
await connectDevice(deviceId, webContents);
|
|
1852
|
-
// A logical operation is now in flight (acquire). Arm the long busy
|
|
1853
|
-
// backstop, NOT the 60s idle clock — the 60s countdown starts only when
|
|
1854
|
-
// the renderer signals logical release (NOBLE_BLE_RELEASE). This keeps
|
|
1855
|
-
// the link up across slow on-device prompts (PIN/passphrase) that have
|
|
1856
|
-
// no outstanding write.
|
|
1857
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1858
1636
|
}
|
|
1859
1637
|
);
|
|
1860
1638
|
|
|
@@ -1866,16 +1644,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1866
1644
|
}
|
|
1867
1645
|
);
|
|
1868
1646
|
|
|
1869
|
-
// Handle logical release: the operation is done, start the 60s idle
|
|
1870
|
-
// countdown so an unused device is freed for other hosts. The physical
|
|
1871
|
-
// link is kept (reused by the next call) unless the countdown elapses.
|
|
1872
|
-
ipcMain.handle(
|
|
1873
|
-
EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE,
|
|
1874
|
-
(_event: IpcMainInvokeEvent, deviceId: string) => {
|
|
1875
|
-
if (connectedDevices.has(deviceId)) armIdleDisconnect(deviceId);
|
|
1876
|
-
}
|
|
1877
|
-
);
|
|
1878
|
-
|
|
1879
1647
|
// Handle write request
|
|
1880
1648
|
ipcMain.handle(
|
|
1881
1649
|
EOneKeyBleMessageKeys.NOBLE_BLE_WRITE,
|
|
@@ -1893,8 +1661,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1893
1661
|
// Send data back to renderer process
|
|
1894
1662
|
webContents.send(EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
|
|
1895
1663
|
});
|
|
1896
|
-
// Still acquiring (in flight) — busy backstop, not the 60s idle clock.
|
|
1897
|
-
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1898
1664
|
}
|
|
1899
1665
|
);
|
|
1900
1666
|
|
|
@@ -1903,7 +1669,6 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1903
1669
|
EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE,
|
|
1904
1670
|
async (_event: IpcMainInvokeEvent, deviceId: string) => {
|
|
1905
1671
|
await unsubscribeNotifications(deviceId);
|
|
1906
|
-
armIdleDisconnect(deviceId);
|
|
1907
1672
|
}
|
|
1908
1673
|
);
|
|
1909
1674
|
|
|
@@ -1949,23 +1714,15 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1949
1714
|
webContents.on('destroyed', () => {
|
|
1950
1715
|
safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
|
|
1951
1716
|
|
|
1952
|
-
//
|
|
1953
|
-
// (tray) and a held link would block the device for every other host.
|
|
1717
|
+
// 1. Clean up all connected devices (unified cleanup, avoid duplicates)
|
|
1954
1718
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
1955
1719
|
deviceIds.forEach(deviceId => {
|
|
1956
|
-
const peripheral = connectedDevices.get(deviceId);
|
|
1957
1720
|
cleanupDevice(deviceId, undefined, {
|
|
1958
1721
|
cleanupConnection: true,
|
|
1959
1722
|
sendDisconnectEvent: false,
|
|
1960
1723
|
cancelOperations: true,
|
|
1961
1724
|
reason: 'app-quit',
|
|
1962
1725
|
});
|
|
1963
|
-
if (peripheral && peripheral.state === 'connected') {
|
|
1964
|
-
peripheral.removeAllListeners('disconnect');
|
|
1965
|
-
peripheral.disconnect(() => {
|
|
1966
|
-
safeLog(logger, 'info', `Disconnected ${deviceId} on window teardown`);
|
|
1967
|
-
});
|
|
1968
|
-
}
|
|
1969
1726
|
});
|
|
1970
1727
|
|
|
1971
1728
|
// 2. Stop scanning
|
package/src/types/desktop-api.ts
CHANGED
|
@@ -8,8 +8,6 @@ export interface NobleBleAPI {
|
|
|
8
8
|
enumerate: () => Promise<{ id: string; name: string }[]>;
|
|
9
9
|
getDevice: (uuid: string) => Promise<{ id: string; name: string } | null>;
|
|
10
10
|
connect: (uuid: string) => Promise<void>;
|
|
11
|
-
/** Logical end-of-operation (keep-alive): starts the idle-disconnect countdown. */
|
|
12
|
-
release?: (uuid: string) => Promise<void>;
|
|
13
11
|
disconnect: (uuid: string) => Promise<void>;
|
|
14
12
|
subscribe: (uuid: string) => Promise<void>;
|
|
15
13
|
unsubscribe: (uuid: string) => Promise<void>;
|
|
@@ -30,15 +30,6 @@ export interface NobleModule {
|
|
|
30
30
|
stopScanning(callback?: () => void): void;
|
|
31
31
|
on(event: 'stateChange', listener: (state: string) => void): void;
|
|
32
32
|
on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
33
|
-
/**
|
|
34
|
-
* Connect by id/address with NO scan. Both native backends support this and
|
|
35
|
-
* emit a `discover` for the peripheral as a side effect (Windows synthesizes
|
|
36
|
-
* one for an unknown address; macOS resolves via
|
|
37
|
-
* retrievePeripheralsWithIdentifiers). Optional so an old noble can omit it.
|
|
38
|
-
* CAUTION: on macOS this never resolves for an id CoreBluetooth cannot
|
|
39
|
-
* retrieve — callers must time-box it.
|
|
40
|
-
*/
|
|
41
|
-
connectAsync?(idOrAddress: string): Promise<Peripheral | undefined>;
|
|
42
33
|
removeListener(event: 'stateChange', listener: (state: string) => void): void;
|
|
43
34
|
removeListener(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
44
35
|
}
|