@onekeyfe/hd-transport-electron 1.2.0-alpha.15 → 1.2.0-alpha.151
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/ble-ops.d.ts +6 -0
- package/dist/ble-ops.d.ts.map +1 -1
- package/dist/ble-packet-capacity.d.ts +2 -0
- package/dist/ble-packet-capacity.d.ts.map +1 -0
- package/dist/{index-55699e30.js → index-70b4a97d.js} +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-16791965.js → noble-ble-handler-efd3df55.js} +455 -232
- package/dist/noble-ble-handler.d.ts +2 -0
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/noble-ble-timeouts.d.ts +3 -0
- package/dist/noble-ble-timeouts.d.ts.map +1 -0
- package/dist/types/desktop-api.d.ts +10 -1
- package/dist/types/desktop-api.d.ts.map +1 -1
- package/dist/types/noble-extended.d.ts +1 -0
- package/dist/types/noble-extended.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/ble-ops.test.ts +21 -0
- package/src/__tests__/ble-packet-capacity.test.ts +13 -0
- package/src/__tests__/noble-ble-handler.test.ts +383 -0
- package/src/ble-ops.ts +40 -10
- package/src/ble-packet-capacity.ts +13 -0
- package/src/index.ts +1 -1
- package/src/noble-ble-handler.ts +532 -250
- package/src/noble-ble-timeouts.ts +2 -0
- package/src/types/desktop-api.ts +10 -2
- package/src/types/noble-extended.ts +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-70b4a97d.js');
|
|
4
4
|
var hdShared = require('@onekeyfe/hd-shared');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
|
|
@@ -8,6 +8,14 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
8
8
|
|
|
9
9
|
var pRetry__default = /*#__PURE__*/_interopDefaultLegacy(pRetry);
|
|
10
10
|
|
|
11
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
12
|
+
function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
|
|
13
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
14
|
+
return fallbackPacketCapacity;
|
|
15
|
+
}
|
|
16
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
function safeLog(logger, level, message, ...args) {
|
|
12
20
|
if (logger) {
|
|
13
21
|
logger[level](message, ...args);
|
|
@@ -17,6 +25,37 @@ function safeLog(logger, level, message, ...args) {
|
|
|
17
25
|
}
|
|
18
26
|
}
|
|
19
27
|
|
|
28
|
+
function runBleCallbackOperation(operation, options) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
let settled = false;
|
|
31
|
+
const settle = (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
clearTimeout(timeout);
|
|
36
|
+
if (error) {
|
|
37
|
+
reject(error);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
resolve();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const timeout = setTimeout(() => {
|
|
44
|
+
if (options.timeoutBehavior === 'reject') {
|
|
45
|
+
settle(new Error(`BLE operation timed out after ${options.timeoutMs}ms`));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
settle();
|
|
49
|
+
}
|
|
50
|
+
}, options.timeoutMs);
|
|
51
|
+
try {
|
|
52
|
+
operation(settle);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
20
59
|
function softRefreshSubscription(params) {
|
|
21
60
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
22
61
|
const { deviceId, notifyCharacteristic, subscriptionOperations, subscribedDevices, pairedDevices, onNotificationData, logger, } = params;
|
|
@@ -25,17 +64,13 @@ function softRefreshSubscription(params) {
|
|
|
25
64
|
}
|
|
26
65
|
logger === null || logger === void 0 ? void 0 : logger.info('[BLE-OPS] Starting subscription refresh', { deviceId });
|
|
27
66
|
subscriptionOperations.set(deviceId, 'subscribing');
|
|
28
|
-
yield
|
|
29
|
-
|
|
67
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
|
|
68
|
+
timeoutMs: 250,
|
|
69
|
+
timeoutBehavior: 'resolve',
|
|
30
70
|
});
|
|
31
|
-
yield
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
reject(error);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
resolve();
|
|
38
|
-
});
|
|
71
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
|
|
72
|
+
timeoutMs: 8000,
|
|
73
|
+
timeoutBehavior: 'reject',
|
|
39
74
|
});
|
|
40
75
|
notifyCharacteristic.removeAllListeners('data');
|
|
41
76
|
notifyCharacteristic.on('data', (data) => {
|
|
@@ -51,6 +86,9 @@ function softRefreshSubscription(params) {
|
|
|
51
86
|
});
|
|
52
87
|
}
|
|
53
88
|
|
|
89
|
+
const NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS = 5000;
|
|
90
|
+
const NOBLE_BLE_CONNECTION_TIMEOUT_MS = 10000;
|
|
91
|
+
|
|
54
92
|
let noble = null;
|
|
55
93
|
let logger = null;
|
|
56
94
|
const bluetoothState = {
|
|
@@ -59,6 +97,7 @@ const bluetoothState = {
|
|
|
59
97
|
initialized: false,
|
|
60
98
|
};
|
|
61
99
|
let persistentStateListener = null;
|
|
100
|
+
let persistentDiscoverListener = null;
|
|
62
101
|
const discoveredDevices = new Map();
|
|
63
102
|
const connectedDevices = new Map();
|
|
64
103
|
const pairedDevices = new Set();
|
|
@@ -66,47 +105,45 @@ const deviceCharacteristics = new Map();
|
|
|
66
105
|
const notificationCallbacks = new Map();
|
|
67
106
|
const subscribedDevices = new Map();
|
|
68
107
|
const subscriptionOperations = new Map();
|
|
108
|
+
const deviceDisconnectListeners = new Map();
|
|
109
|
+
const deviceMtuListeners = new Map();
|
|
69
110
|
const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
111
|
+
const ONEKEY_SERVICE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_SERVICE_UUID);
|
|
112
|
+
const ONEKEY_WRITE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID);
|
|
113
|
+
const ONEKEY_NOTIFY_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_NOTIFY_CHARACTERISTIC_UUID);
|
|
73
114
|
const BLUETOOTH_INIT_TIMEOUT = 10000;
|
|
74
|
-
const DEVICE_SCAN_TIMEOUT =
|
|
75
|
-
const FAST_SCAN_TIMEOUT = 8000;
|
|
115
|
+
const DEVICE_SCAN_TIMEOUT = 5000;
|
|
76
116
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
77
|
-
const CONNECTION_TIMEOUT = 8000;
|
|
78
117
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
79
|
-
const
|
|
80
|
-
const
|
|
118
|
+
const BLE_CLEANUP_TIMEOUT = 250;
|
|
119
|
+
const BLE_IDLE_DISCONNECT_MS = 3 * 60000;
|
|
120
|
+
const BLE_BUSY_BACKSTOP_MS = 10 * 60000;
|
|
121
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
122
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
123
|
+
const DEFAULT_WRITE_PACING_DELAY_MS = 5;
|
|
81
124
|
const RETRY_CONFIG = { MAX_ATTEMPTS: 15, WRITE_TIMEOUT: 2000 };
|
|
82
125
|
const IS_WINDOWS = process.platform === 'win32';
|
|
83
126
|
const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
84
127
|
/status:\s*3/i,
|
|
85
128
|
];
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const NORMALIZED_ONEKEY_SERVICE_UUIDS = new Set([
|
|
91
|
-
...ONEKEY_SERVICE_UUIDS.map(uuid => getBleUuidKey(uuid)),
|
|
92
|
-
'0001',
|
|
93
|
-
]);
|
|
94
|
-
function isGenericBleService(uuid) {
|
|
95
|
-
return ['1800', '1801', '180a', '180f'].includes(getBleUuidKey(uuid));
|
|
96
|
-
}
|
|
97
|
-
function hasOneKeyAdvertisementService(peripheral) {
|
|
98
|
-
var _a, _b;
|
|
99
|
-
const serviceUuids = (_b = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids) !== null && _b !== void 0 ? _b : [];
|
|
100
|
-
return serviceUuids.some(uuid => {
|
|
101
|
-
const uuidKey = getBleUuidKey(uuid);
|
|
102
|
-
return (NORMALIZED_ONEKEY_SERVICE_UUIDS.has(uuidKey) ||
|
|
103
|
-
PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(uuidKey));
|
|
104
|
-
});
|
|
129
|
+
function resolveNobleBleWritePacingDelay(options) {
|
|
130
|
+
return typeof (options === null || options === void 0 ? void 0 : options.pacingDelayMs) === 'number' && Number.isFinite(options.pacingDelayMs)
|
|
131
|
+
? Math.min(Math.max(Math.floor(options.pacingDelayMs), 0), 1000)
|
|
132
|
+
: DEFAULT_WRITE_PACING_DELAY_MS;
|
|
105
133
|
}
|
|
106
134
|
function isOneKeyPeripheral(peripheral) {
|
|
107
|
-
var _a;
|
|
108
|
-
const
|
|
109
|
-
|
|
135
|
+
var _a, _b;
|
|
136
|
+
const serviceUuids = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids;
|
|
137
|
+
const localName = (_b = peripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName;
|
|
138
|
+
if (!(localName === null || localName === void 0 ? void 0 : localName.trim())) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return (hdShared.hasOnekeyCommunicationService(serviceUuids) &&
|
|
142
|
+
hdShared.isOnekeyBluetoothDevice({
|
|
143
|
+
id: peripheral.id,
|
|
144
|
+
localName,
|
|
145
|
+
serviceUuids,
|
|
146
|
+
}));
|
|
110
147
|
}
|
|
111
148
|
function emitRawNotification(deviceId, data) {
|
|
112
149
|
const appCb = notificationCallbacks.get(deviceId);
|
|
@@ -205,6 +242,7 @@ function initializeNoble() {
|
|
|
205
242
|
try {
|
|
206
243
|
noble = require('@stoprocent/noble');
|
|
207
244
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble library loaded');
|
|
245
|
+
setupPersistentStateListener();
|
|
208
246
|
yield new Promise((resolve, reject) => {
|
|
209
247
|
if (!noble) {
|
|
210
248
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not initialized'));
|
|
@@ -214,7 +252,6 @@ function initializeNoble() {
|
|
|
214
252
|
resolve();
|
|
215
253
|
return;
|
|
216
254
|
}
|
|
217
|
-
setupPersistentStateListener();
|
|
218
255
|
const timeout = setTimeout(() => {
|
|
219
256
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout'));
|
|
220
257
|
}, BLUETOOTH_INIT_TIMEOUT);
|
|
@@ -245,9 +282,12 @@ function initializeNoble() {
|
|
|
245
282
|
};
|
|
246
283
|
noble.on('stateChange', onStateChange);
|
|
247
284
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
285
|
+
if (!persistentDiscoverListener) {
|
|
286
|
+
persistentDiscoverListener = (peripheral) => {
|
|
287
|
+
handleDeviceDiscovered(peripheral);
|
|
288
|
+
};
|
|
289
|
+
noble.on('discover', persistentDiscoverListener);
|
|
290
|
+
}
|
|
251
291
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Noble initialized successfully');
|
|
252
292
|
}
|
|
253
293
|
catch (error) {
|
|
@@ -258,6 +298,62 @@ function initializeNoble() {
|
|
|
258
298
|
}
|
|
259
299
|
});
|
|
260
300
|
}
|
|
301
|
+
const idleDisconnectTimers = new Map();
|
|
302
|
+
const idleDisconnectInFlight = new Map();
|
|
303
|
+
function clearIdleDisconnect(deviceId) {
|
|
304
|
+
const timer = idleDisconnectTimers.get(deviceId);
|
|
305
|
+
if (timer) {
|
|
306
|
+
clearTimeout(timer);
|
|
307
|
+
idleDisconnectTimers.delete(deviceId);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function awaitIdleDisconnect(deviceId) {
|
|
311
|
+
return index.__awaiter(this, void 0, void 0, function* () {
|
|
312
|
+
const pending = idleDisconnectInFlight.get(deviceId);
|
|
313
|
+
if (pending)
|
|
314
|
+
yield pending.catch(() => undefined);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function broadcastToAllWebContents(channel, payload) {
|
|
318
|
+
try {
|
|
319
|
+
const { webContents: electronWebContents } = require('electron');
|
|
320
|
+
for (const wc of electronWebContents.getAllWebContents()) {
|
|
321
|
+
if (!wc.isDestroyed())
|
|
322
|
+
wc.send(channel, payload);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] broadcast failed:', { channel, error: String(error) });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function armIdleDisconnect(deviceId, ms = BLE_IDLE_DISCONNECT_MS, reason = 'idle') {
|
|
330
|
+
clearIdleDisconnect(deviceId);
|
|
331
|
+
idleDisconnectTimers.set(deviceId, setTimeout(() => {
|
|
332
|
+
var _a;
|
|
333
|
+
idleDisconnectTimers.delete(deviceId);
|
|
334
|
+
if (!connectedDevices.has(deviceId))
|
|
335
|
+
return;
|
|
336
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Keep-alive timeout, disconnecting device', { deviceId, reason });
|
|
337
|
+
const peripheral = connectedDevices.get(deviceId);
|
|
338
|
+
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
339
|
+
const pending = disconnectDevice(deviceId)
|
|
340
|
+
.then(() => {
|
|
341
|
+
broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
|
|
342
|
+
id: deviceId,
|
|
343
|
+
name: deviceName,
|
|
344
|
+
});
|
|
345
|
+
})
|
|
346
|
+
.catch(error => {
|
|
347
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Keep-alive disconnect failed', { deviceId, error });
|
|
348
|
+
})
|
|
349
|
+
.finally(() => {
|
|
350
|
+
if (idleDisconnectInFlight.get(deviceId) === pending) {
|
|
351
|
+
idleDisconnectInFlight.delete(deviceId);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
idleDisconnectInFlight.set(deviceId, pending);
|
|
355
|
+
}, ms));
|
|
356
|
+
}
|
|
261
357
|
function cleanupDevice(deviceId, webContents, options = {}) {
|
|
262
358
|
var _a;
|
|
263
359
|
const { cleanupConnection = true, cleanupDiscoveredCache = false, sendDisconnectEvent = false, cancelOperations = true, reason = 'unknown', } = options;
|
|
@@ -269,9 +365,20 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
269
365
|
sendDisconnectEvent,
|
|
270
366
|
cancelOperations,
|
|
271
367
|
});
|
|
368
|
+
clearIdleDisconnect(deviceId);
|
|
272
369
|
const peripheral = connectedDevices.get(deviceId);
|
|
273
370
|
const deviceName = ((_a = peripheral === null || peripheral === void 0 ? void 0 : peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
274
371
|
if (cleanupConnection) {
|
|
372
|
+
const disconnectEntry = deviceDisconnectListeners.get(deviceId);
|
|
373
|
+
if (disconnectEntry) {
|
|
374
|
+
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
375
|
+
deviceDisconnectListeners.delete(deviceId);
|
|
376
|
+
}
|
|
377
|
+
const mtuEntry = deviceMtuListeners.get(deviceId);
|
|
378
|
+
if (mtuEntry) {
|
|
379
|
+
mtuEntry.peripheral.removeListener('mtu', mtuEntry.listener);
|
|
380
|
+
deviceMtuListeners.delete(deviceId);
|
|
381
|
+
}
|
|
275
382
|
connectedDevices.delete(deviceId);
|
|
276
383
|
deviceCharacteristics.delete(deviceId);
|
|
277
384
|
notificationCallbacks.delete(deviceId);
|
|
@@ -282,8 +389,8 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
282
389
|
if (cleanupDiscoveredCache) {
|
|
283
390
|
discoveredDevices.delete(deviceId);
|
|
284
391
|
}
|
|
285
|
-
if (sendDisconnectEvent
|
|
286
|
-
|
|
392
|
+
if (sendDisconnectEvent) {
|
|
393
|
+
broadcastToAllWebContents(hdShared.EOneKeyBleMessageKeys.BLE_DEVICE_DISCONNECTED, {
|
|
287
394
|
id: deviceId,
|
|
288
395
|
name: deviceName,
|
|
289
396
|
});
|
|
@@ -300,18 +407,43 @@ function handleDeviceDisconnect(deviceId, webContents) {
|
|
|
300
407
|
});
|
|
301
408
|
cleanupDevice(deviceId, webContents, {
|
|
302
409
|
cleanupConnection: true,
|
|
410
|
+
cleanupDiscoveredCache: true,
|
|
303
411
|
sendDisconnectEvent: true,
|
|
304
412
|
cancelOperations: true,
|
|
305
413
|
reason: 'auto-disconnect',
|
|
306
414
|
});
|
|
307
415
|
}
|
|
308
416
|
function setupDisconnectListener(peripheral, deviceId, webContents) {
|
|
309
|
-
|
|
310
|
-
|
|
417
|
+
const existing = deviceDisconnectListeners.get(deviceId);
|
|
418
|
+
if (existing) {
|
|
419
|
+
existing.peripheral.removeListener('disconnect', existing.listener);
|
|
420
|
+
}
|
|
421
|
+
const listener = () => {
|
|
311
422
|
handleDeviceDisconnect(deviceId, webContents);
|
|
312
|
-
}
|
|
423
|
+
};
|
|
424
|
+
deviceDisconnectListeners.set(deviceId, { peripheral, listener });
|
|
425
|
+
peripheral.on('disconnect', listener);
|
|
426
|
+
setupMtuListener(peripheral, deviceId, webContents);
|
|
313
427
|
}
|
|
314
|
-
function
|
|
428
|
+
function setupMtuListener(peripheral, deviceId, webContents) {
|
|
429
|
+
const existing = deviceMtuListeners.get(deviceId);
|
|
430
|
+
if (existing) {
|
|
431
|
+
existing.peripheral.removeListener('mtu', existing.listener);
|
|
432
|
+
}
|
|
433
|
+
const listener = (mtu) => {
|
|
434
|
+
if (!Number.isFinite(mtu) || mtu <= 0)
|
|
435
|
+
return;
|
|
436
|
+
if (webContents.isDestroyed())
|
|
437
|
+
return;
|
|
438
|
+
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
|
|
439
|
+
id: deviceId,
|
|
440
|
+
mtu,
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
deviceMtuListeners.set(deviceId, { peripheral, listener });
|
|
444
|
+
peripheral.on('mtu', listener);
|
|
445
|
+
}
|
|
446
|
+
function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
|
|
315
447
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
316
448
|
return new Promise((resolve, reject) => {
|
|
317
449
|
writeCharacteristic.write(buffer, true, (error) => {
|
|
@@ -343,7 +475,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
343
475
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Write characteristic not available for ${deviceId}`);
|
|
344
476
|
}
|
|
345
477
|
try {
|
|
346
|
-
yield
|
|
478
|
+
yield writeCharacteristicWithoutResponse(deviceId, latestWrite, payload);
|
|
347
479
|
}
|
|
348
480
|
catch (e) {
|
|
349
481
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
@@ -402,52 +534,57 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
402
534
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `No response observed after ${RETRY_CONFIG.MAX_ATTEMPTS} writes: ${deviceId}`);
|
|
403
535
|
});
|
|
404
536
|
}
|
|
405
|
-
function transmitHexDataToDevice(deviceId, hexData) {
|
|
537
|
+
function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
406
538
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
407
539
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
408
540
|
const peripheral = connectedDevices.get(deviceId);
|
|
409
541
|
if (!peripheral || !characteristics) {
|
|
410
542
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
|
|
411
543
|
}
|
|
544
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
412
545
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
413
546
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
547
|
+
const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
548
|
+
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
414
549
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
415
550
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
416
551
|
if (!writeCharacteristic) {
|
|
417
552
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
418
553
|
}
|
|
419
|
-
if (toBuffer.length <=
|
|
420
|
-
|
|
421
|
-
|
|
554
|
+
if (toBuffer.length <= packetCapacity) {
|
|
555
|
+
if (pacingDelayMs > 0)
|
|
556
|
+
yield hdShared.wait(pacingDelayMs);
|
|
557
|
+
yield writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, toBuffer);
|
|
422
558
|
return;
|
|
423
559
|
}
|
|
424
560
|
for (let offset = 0; offset < toBuffer.length;) {
|
|
425
|
-
const chunkSize = Math.min(
|
|
561
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
426
562
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
427
563
|
offset += chunkSize;
|
|
428
564
|
const latest = doGetWriteCharacteristic();
|
|
429
565
|
if (!latest) {
|
|
430
566
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
431
567
|
}
|
|
432
|
-
yield
|
|
433
|
-
if (offset < toBuffer.length) {
|
|
434
|
-
yield hdShared.wait(
|
|
568
|
+
yield writeCharacteristicWithoutResponse(deviceId, latest, chunk);
|
|
569
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
570
|
+
yield hdShared.wait(pacingDelayMs);
|
|
435
571
|
}
|
|
436
572
|
}
|
|
437
573
|
return;
|
|
438
574
|
}
|
|
439
|
-
if (toBuffer.length <=
|
|
440
|
-
|
|
575
|
+
if (toBuffer.length <= packetCapacity) {
|
|
576
|
+
if (pacingDelayMs > 0)
|
|
577
|
+
yield hdShared.wait(pacingDelayMs);
|
|
441
578
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
|
|
442
579
|
return;
|
|
443
580
|
}
|
|
444
581
|
for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
|
|
445
|
-
const chunkSize = Math.min(
|
|
582
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
446
583
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
447
584
|
offset += chunkSize;
|
|
448
585
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, chunk, `chunk-${idx + 1}`);
|
|
449
|
-
if (offset < toBuffer.length) {
|
|
450
|
-
yield hdShared.wait(
|
|
586
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
587
|
+
yield hdShared.wait(pacingDelayMs);
|
|
451
588
|
}
|
|
452
589
|
}
|
|
453
590
|
});
|
|
@@ -470,17 +607,25 @@ function handleDeviceDiscovered(peripheral) {
|
|
|
470
607
|
function ensureDiscoverListener() {
|
|
471
608
|
if (!noble)
|
|
472
609
|
return;
|
|
473
|
-
|
|
474
|
-
if (listenerCount === 0) {
|
|
610
|
+
if (!persistentDiscoverListener) {
|
|
475
611
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discover listener missing, re-adding it');
|
|
476
|
-
|
|
612
|
+
persistentDiscoverListener = (peripheral) => {
|
|
477
613
|
handleDeviceDiscovered(peripheral);
|
|
478
|
-
}
|
|
614
|
+
};
|
|
615
|
+
noble.on('discover', persistentDiscoverListener);
|
|
479
616
|
}
|
|
480
617
|
else {
|
|
481
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already
|
|
618
|
+
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already registered');
|
|
482
619
|
}
|
|
483
620
|
}
|
|
621
|
+
function waitForNobleScanStop(nobleInstance) {
|
|
622
|
+
return index.__awaiter(this, void 0, void 0, function* () {
|
|
623
|
+
yield runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
|
|
624
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
625
|
+
timeoutBehavior: 'resolve',
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
484
629
|
function performTargetedScan(targetDeviceId) {
|
|
485
630
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
486
631
|
if (!noble) {
|
|
@@ -489,33 +634,43 @@ function performTargetedScan(targetDeviceId) {
|
|
|
489
634
|
const nobleInstance = noble;
|
|
490
635
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
|
|
491
636
|
return new Promise((resolve, reject) => {
|
|
637
|
+
let settled = false;
|
|
638
|
+
const finish = (peripheral, error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
639
|
+
if (settled)
|
|
640
|
+
return;
|
|
641
|
+
settled = true;
|
|
642
|
+
if (timeoutId)
|
|
643
|
+
clearTimeout(timeoutId);
|
|
644
|
+
nobleInstance.removeListener('discover', onDiscover);
|
|
645
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
646
|
+
if (error) {
|
|
647
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
|
|
648
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (peripheral) {
|
|
652
|
+
discoveredDevices.set(peripheral.id, peripheral);
|
|
653
|
+
}
|
|
654
|
+
resolve(peripheral);
|
|
655
|
+
});
|
|
492
656
|
const onDiscover = (peripheral) => {
|
|
493
657
|
var _a;
|
|
494
|
-
if (peripheral.id === targetDeviceId) {
|
|
658
|
+
if (peripheral.id === targetDeviceId && isOneKeyPeripheral(peripheral)) {
|
|
495
659
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Target device found during targeted scan:', {
|
|
496
660
|
id: peripheral.id,
|
|
497
661
|
name: (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName,
|
|
498
662
|
});
|
|
499
|
-
|
|
500
|
-
nobleInstance.removeListener('discover', onDiscover);
|
|
501
|
-
nobleInstance.stopScanning();
|
|
502
|
-
discoveredDevices.set(peripheral.id, peripheral);
|
|
503
|
-
resolve(peripheral);
|
|
663
|
+
finish(peripheral).catch(reject);
|
|
504
664
|
}
|
|
505
665
|
};
|
|
506
666
|
const timeoutId = setTimeout(() => {
|
|
507
|
-
nobleInstance.removeListener('discover', onDiscover);
|
|
508
|
-
nobleInstance.stopScanning();
|
|
509
667
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
510
|
-
|
|
511
|
-
},
|
|
668
|
+
finish(null).catch(reject);
|
|
669
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
512
670
|
nobleInstance.on('discover', onDiscover);
|
|
513
|
-
nobleInstance.startScanning([],
|
|
671
|
+
nobleInstance.startScanning([], true, (error) => {
|
|
514
672
|
if (error) {
|
|
515
|
-
|
|
516
|
-
nobleInstance.removeListener('discover', onDiscover);
|
|
517
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
|
|
518
|
-
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
673
|
+
finish(null, error).catch(reject);
|
|
519
674
|
return;
|
|
520
675
|
}
|
|
521
676
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
|
|
@@ -538,44 +693,44 @@ function enumerateDevices() {
|
|
|
538
693
|
return new Promise((resolve, reject) => {
|
|
539
694
|
const devices = [];
|
|
540
695
|
let intervalId;
|
|
541
|
-
const cleanup = () => {
|
|
696
|
+
const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
542
697
|
clearTimeout(timeoutId);
|
|
543
698
|
if (intervalId)
|
|
544
699
|
clearInterval(intervalId);
|
|
545
|
-
nobleInstance
|
|
700
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
701
|
+
});
|
|
702
|
+
const pushDevice = (peripheral, id) => {
|
|
703
|
+
var _a;
|
|
704
|
+
if (devices.some(d => d.id === id))
|
|
705
|
+
return;
|
|
706
|
+
devices.push({
|
|
707
|
+
commType: 'electron-ble',
|
|
708
|
+
id,
|
|
709
|
+
name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
|
|
710
|
+
state: peripheral.state || 'disconnected',
|
|
711
|
+
});
|
|
546
712
|
};
|
|
547
713
|
const checkDevices = () => {
|
|
548
|
-
discoveredDevices.forEach(
|
|
549
|
-
|
|
550
|
-
const existingDevice = devices.find(d => d.id === id);
|
|
551
|
-
if (!existingDevice) {
|
|
552
|
-
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
553
|
-
devices.push({
|
|
554
|
-
commType: 'electron-ble',
|
|
555
|
-
id,
|
|
556
|
-
name: deviceName,
|
|
557
|
-
state: peripheral.state || 'disconnected',
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
});
|
|
714
|
+
discoveredDevices.forEach(pushDevice);
|
|
715
|
+
connectedDevices.forEach(pushDevice);
|
|
561
716
|
};
|
|
562
|
-
const timeoutId = setTimeout(() => {
|
|
717
|
+
const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
|
|
563
718
|
checkDevices();
|
|
564
|
-
cleanup();
|
|
719
|
+
yield cleanup();
|
|
565
720
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
566
721
|
resolve(devices);
|
|
567
|
-
}, DEVICE_SCAN_TIMEOUT);
|
|
722
|
+
}), DEVICE_SCAN_TIMEOUT);
|
|
568
723
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
569
|
-
nobleInstance.startScanning([],
|
|
724
|
+
nobleInstance.startScanning([], true, (error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
570
725
|
if (error) {
|
|
571
|
-
cleanup();
|
|
726
|
+
yield cleanup();
|
|
572
727
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
573
728
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
574
729
|
return;
|
|
575
730
|
}
|
|
576
731
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning started for OneKey devices');
|
|
577
732
|
intervalId = setInterval(checkDevices, DEVICE_CHECK_INTERVAL);
|
|
578
|
-
});
|
|
733
|
+
}));
|
|
579
734
|
});
|
|
580
735
|
});
|
|
581
736
|
}
|
|
@@ -583,25 +738,20 @@ function stopScanning() {
|
|
|
583
738
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
584
739
|
if (!noble)
|
|
585
740
|
return;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
noble.stopScanning(() => {
|
|
592
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
|
|
593
|
-
resolve();
|
|
594
|
-
});
|
|
595
|
-
});
|
|
741
|
+
const nobleInstance = noble;
|
|
742
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
743
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
|
|
596
744
|
});
|
|
597
745
|
}
|
|
598
746
|
function cleanupNobleListeners() {
|
|
599
747
|
if (!noble)
|
|
600
748
|
return;
|
|
601
749
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
750
|
+
if (persistentDiscoverListener) {
|
|
751
|
+
noble.removeListener('discover', persistentDiscoverListener);
|
|
752
|
+
persistentDiscoverListener = null;
|
|
753
|
+
}
|
|
754
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Owned Noble listeners cleaned up');
|
|
605
755
|
}
|
|
606
756
|
catch (error) {
|
|
607
757
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to clean up some listeners:', error);
|
|
@@ -612,22 +762,12 @@ function getDevice(deviceId) {
|
|
|
612
762
|
const peripheral = discoveredDevices.get(deviceId);
|
|
613
763
|
if (peripheral) {
|
|
614
764
|
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
615
|
-
return {
|
|
616
|
-
commType: 'electron-ble',
|
|
617
|
-
id: peripheral.id,
|
|
618
|
-
name: deviceName,
|
|
619
|
-
state: peripheral.state || 'disconnected',
|
|
620
|
-
};
|
|
765
|
+
return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}));
|
|
621
766
|
}
|
|
622
767
|
const connectedPeripheral = connectedDevices.get(deviceId);
|
|
623
768
|
if (connectedPeripheral) {
|
|
624
769
|
const deviceName = ((_b = connectedPeripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device';
|
|
625
|
-
return {
|
|
626
|
-
commType: 'electron-ble',
|
|
627
|
-
id: connectedPeripheral.id,
|
|
628
|
-
name: deviceName,
|
|
629
|
-
state: connectedPeripheral.state || 'connected',
|
|
630
|
-
};
|
|
770
|
+
return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}));
|
|
631
771
|
}
|
|
632
772
|
return {
|
|
633
773
|
commType: 'electron-ble',
|
|
@@ -678,14 +818,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
678
818
|
deviceId: peripheral.id,
|
|
679
819
|
serviceUUIDs: services.map(service => service.uuid),
|
|
680
820
|
});
|
|
681
|
-
|
|
682
|
-
if (!service) {
|
|
683
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Known OneKey service UUID not found, trying first vendor service');
|
|
684
|
-
service =
|
|
685
|
-
services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
|
|
686
|
-
services.find(s => !isGenericBleService(s.uuid)) ||
|
|
687
|
-
services[0];
|
|
688
|
-
}
|
|
821
|
+
const service = services.find(s => hdShared.matchesKnownBleUuid(s.uuid, ONEKEY_SERVICE_UUID_ALIASES));
|
|
689
822
|
if (!service) {
|
|
690
823
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
691
824
|
}
|
|
@@ -708,12 +841,10 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
708
841
|
let writeCharacteristic = null;
|
|
709
842
|
let notifyCharacteristic = null;
|
|
710
843
|
for (const characteristic of characteristics) {
|
|
711
|
-
|
|
712
|
-
const uuidKey = uuid.length >= 8 ? uuid.substring(4, 8) : uuid;
|
|
713
|
-
if (uuidKey === NORMALIZED_WRITE_UUID) {
|
|
844
|
+
if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_WRITE_UUID_ALIASES)) {
|
|
714
845
|
writeCharacteristic = characteristic;
|
|
715
846
|
}
|
|
716
|
-
else if (
|
|
847
|
+
else if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_NOTIFY_UUID_ALIASES)) {
|
|
717
848
|
notifyCharacteristic = characteristic;
|
|
718
849
|
}
|
|
719
850
|
}
|
|
@@ -747,27 +878,17 @@ function forceReconnectPeripheral(peripheral, deviceId) {
|
|
|
747
878
|
reason: 'force-reconnect',
|
|
748
879
|
});
|
|
749
880
|
if (peripheral.state === 'connected') {
|
|
750
|
-
peripheral.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
resolve();
|
|
755
|
-
});
|
|
756
|
-
});
|
|
881
|
+
yield runBleCallbackOperation(callback => peripheral.disconnect(() => {
|
|
882
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
|
|
883
|
+
callback();
|
|
884
|
+
}), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
|
|
757
885
|
}
|
|
758
|
-
yield
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Force reconnect failed:', error);
|
|
762
|
-
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
|
|
763
|
-
}
|
|
764
|
-
else {
|
|
765
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
|
|
766
|
-
connectedDevices.set(deviceId, peripheral);
|
|
767
|
-
resolve();
|
|
768
|
-
}
|
|
769
|
-
});
|
|
886
|
+
yield runBleCallbackOperation(callback => peripheral.connect(callback), {
|
|
887
|
+
timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
|
|
888
|
+
timeoutBehavior: 'reject',
|
|
770
889
|
});
|
|
890
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
|
|
891
|
+
connectedDevices.set(deviceId, peripheral);
|
|
771
892
|
yield hdShared.wait(500);
|
|
772
893
|
});
|
|
773
894
|
}
|
|
@@ -844,18 +965,83 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
844
965
|
}
|
|
845
966
|
function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
846
967
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
968
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
969
|
+
try {
|
|
970
|
+
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
971
|
+
}
|
|
972
|
+
catch (initialError) {
|
|
973
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
|
|
974
|
+
deviceId,
|
|
975
|
+
error: initialError,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
847
978
|
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
848
979
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
849
980
|
try {
|
|
850
981
|
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
851
982
|
}
|
|
852
|
-
catch (
|
|
853
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...',
|
|
983
|
+
catch (reconnectError) {
|
|
984
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...', reconnectError);
|
|
854
985
|
return freshScanAndDiscover(deviceId, webContents);
|
|
855
986
|
}
|
|
856
987
|
});
|
|
857
988
|
}
|
|
989
|
+
const DIRECT_CONNECT_TIMEOUT_MS = 2000;
|
|
990
|
+
const DIRECT_CONNECT_COOLDOWN_MS = 15000;
|
|
991
|
+
const directConnectCooldownUntil = new Map();
|
|
992
|
+
function tryDirectConnectById(deviceId) {
|
|
993
|
+
var _a;
|
|
994
|
+
return index.__awaiter(this, void 0, void 0, function* () {
|
|
995
|
+
const nobleInstance = noble;
|
|
996
|
+
if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
|
|
997
|
+
return undefined;
|
|
998
|
+
const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
|
|
999
|
+
if (Date.now() < cooldownUntil)
|
|
1000
|
+
return undefined;
|
|
1001
|
+
try {
|
|
1002
|
+
const directPromise = nobleInstance.connectAsync(deviceId);
|
|
1003
|
+
const raced = yield Promise.race([
|
|
1004
|
+
directPromise,
|
|
1005
|
+
new Promise(resolve => {
|
|
1006
|
+
setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
|
|
1007
|
+
}),
|
|
1008
|
+
]);
|
|
1009
|
+
if (raced === 'timeout') {
|
|
1010
|
+
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1011
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
|
|
1012
|
+
deviceId,
|
|
1013
|
+
});
|
|
1014
|
+
directPromise
|
|
1015
|
+
.then(late => {
|
|
1016
|
+
const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
|
|
1017
|
+
if (latePeripheral &&
|
|
1018
|
+
latePeripheral.state === 'connected' &&
|
|
1019
|
+
!connectedDevices.has(deviceId)) {
|
|
1020
|
+
latePeripheral.removeAllListeners('disconnect');
|
|
1021
|
+
latePeripheral.disconnect(() => undefined);
|
|
1022
|
+
}
|
|
1023
|
+
})
|
|
1024
|
+
.catch(() => undefined);
|
|
1025
|
+
return undefined;
|
|
1026
|
+
}
|
|
1027
|
+
const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
|
|
1028
|
+
if (!peripheral || peripheral.state !== 'connected')
|
|
1029
|
+
return undefined;
|
|
1030
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id succeeded', { deviceId });
|
|
1031
|
+
return peripheral;
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1035
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id failed, falling back to scan', {
|
|
1036
|
+
deviceId,
|
|
1037
|
+
error: String(error),
|
|
1038
|
+
});
|
|
1039
|
+
return undefined;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
858
1043
|
function connectDevice(deviceId, webContents) {
|
|
1044
|
+
var _a;
|
|
859
1045
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
860
1046
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
861
1047
|
deviceId,
|
|
@@ -865,7 +1051,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
865
1051
|
totalDiscovered: discoveredDevices.size,
|
|
866
1052
|
totalConnected: connectedDevices.size,
|
|
867
1053
|
});
|
|
868
|
-
let peripheral = discoveredDevices.get(deviceId);
|
|
1054
|
+
let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
|
|
869
1055
|
if (!peripheral) {
|
|
870
1056
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
871
1057
|
if (!noble) {
|
|
@@ -874,6 +1060,12 @@ function connectDevice(deviceId, webContents) {
|
|
|
874
1060
|
if (!noble) {
|
|
875
1061
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
876
1062
|
}
|
|
1063
|
+
peripheral = yield tryDirectConnectById(deviceId);
|
|
1064
|
+
if (peripheral) {
|
|
1065
|
+
discoveredDevices.set(deviceId, peripheral);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (!peripheral) {
|
|
877
1069
|
try {
|
|
878
1070
|
const foundPeripheral = yield performTargetedScan(deviceId);
|
|
879
1071
|
if (!foundPeripheral) {
|
|
@@ -894,8 +1086,8 @@ function connectDevice(deviceId, webContents) {
|
|
|
894
1086
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
|
|
895
1087
|
if (!connectedDevices.has(deviceId)) {
|
|
896
1088
|
connectedDevices.set(deviceId, peripheral);
|
|
897
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
898
1089
|
}
|
|
1090
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
899
1091
|
if (deviceCharacteristics.has(deviceId)) {
|
|
900
1092
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
|
|
901
1093
|
const ongoingOperation = subscriptionOperations.get(deviceId);
|
|
@@ -917,18 +1109,37 @@ function connectDevice(deviceId, webContents) {
|
|
|
917
1109
|
notificationCallbacks.delete(deviceId);
|
|
918
1110
|
subscribedDevices.delete(deviceId);
|
|
919
1111
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1112
|
+
try {
|
|
1113
|
+
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
1114
|
+
deviceCharacteristics.set(deviceId, characteristics);
|
|
1115
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
|
|
1116
|
+
}
|
|
1117
|
+
catch (setupError) {
|
|
1118
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed on kept-alive link:', setupError);
|
|
1119
|
+
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1120
|
+
throw setupError;
|
|
1121
|
+
}
|
|
923
1122
|
return;
|
|
924
1123
|
}
|
|
925
1124
|
return new Promise((resolve, reject) => {
|
|
1125
|
+
let connectionTimedOut = false;
|
|
926
1126
|
const timeout = setTimeout(() => {
|
|
1127
|
+
connectionTimedOut = true;
|
|
927
1128
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
|
|
928
|
-
},
|
|
1129
|
+
}, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
|
|
929
1130
|
const connectedPeripheral = peripheral;
|
|
930
1131
|
connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
931
1132
|
clearTimeout(timeout);
|
|
1133
|
+
if (connectionTimedOut) {
|
|
1134
|
+
if (!error) {
|
|
1135
|
+
try {
|
|
1136
|
+
connectedPeripheral.disconnect(() => undefined);
|
|
1137
|
+
}
|
|
1138
|
+
catch (_a) {
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
932
1143
|
if (error) {
|
|
933
1144
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
|
|
934
1145
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
|
|
@@ -958,17 +1169,21 @@ function disconnectDevice(deviceId) {
|
|
|
958
1169
|
if (!peripheral) {
|
|
959
1170
|
return;
|
|
960
1171
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
peripheral.disconnect
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1172
|
+
const disconnectEntry = deviceDisconnectListeners.get(deviceId);
|
|
1173
|
+
if (disconnectEntry) {
|
|
1174
|
+
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
1175
|
+
deviceDisconnectListeners.delete(deviceId);
|
|
1176
|
+
}
|
|
1177
|
+
yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1178
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1179
|
+
timeoutBehavior: 'resolve',
|
|
1180
|
+
});
|
|
1181
|
+
cleanupDevice(deviceId, undefined, {
|
|
1182
|
+
cleanupConnection: true,
|
|
1183
|
+
cleanupDiscoveredCache: true,
|
|
1184
|
+
sendDisconnectEvent: false,
|
|
1185
|
+
cancelOperations: true,
|
|
1186
|
+
reason: 'manual-disconnect',
|
|
972
1187
|
});
|
|
973
1188
|
});
|
|
974
1189
|
}
|
|
@@ -983,18 +1198,11 @@ function unsubscribeNotifications(deviceId) {
|
|
|
983
1198
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Unsubscribing from notifications for device:', deviceId);
|
|
984
1199
|
subscriptionOperations.set(deviceId, 'unsubscribing');
|
|
985
1200
|
try {
|
|
986
|
-
yield
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Notification unsubscription failed:', error);
|
|
990
|
-
reject(error);
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription successful');
|
|
994
|
-
resolve();
|
|
995
|
-
}
|
|
996
|
-
});
|
|
1201
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
|
|
1202
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1203
|
+
timeoutBehavior: 'resolve',
|
|
997
1204
|
});
|
|
1205
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription completed');
|
|
998
1206
|
notifyCharacteristic.removeAllListeners('data');
|
|
999
1207
|
notificationCallbacks.delete(deviceId);
|
|
1000
1208
|
subscribedDevices.delete(deviceId);
|
|
@@ -1046,19 +1254,13 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1046
1254
|
notificationCallbacks.set(deviceId, callback);
|
|
1047
1255
|
function rebuildAppSubscription(deviceId, notifyCharacteristic) {
|
|
1048
1256
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1049
|
-
yield
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
});
|
|
1257
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
|
|
1258
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1259
|
+
timeoutBehavior: 'resolve',
|
|
1053
1260
|
});
|
|
1054
|
-
yield
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
reject(error);
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
resolve();
|
|
1061
|
-
});
|
|
1261
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
|
|
1262
|
+
timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
|
|
1263
|
+
timeoutBehavior: 'reject',
|
|
1062
1264
|
});
|
|
1063
1265
|
notifyCharacteristic.on('data', (data) => {
|
|
1064
1266
|
if (!pairedDevices.has(deviceId)) {
|
|
@@ -1082,8 +1284,12 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1082
1284
|
try {
|
|
1083
1285
|
logger = require('electron-log');
|
|
1084
1286
|
const { ipcMain } = require('electron');
|
|
1287
|
+
const handle = (channel, listener) => {
|
|
1288
|
+
ipcMain.removeHandler(channel);
|
|
1289
|
+
ipcMain.handle(channel, listener);
|
|
1290
|
+
};
|
|
1085
1291
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1086
|
-
|
|
1292
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1087
1293
|
try {
|
|
1088
1294
|
const devices = yield enumerateDevices();
|
|
1089
1295
|
safeLog(logger, 'debug', 'Enumeration completed', {
|
|
@@ -1097,34 +1303,54 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1097
1303
|
throw error;
|
|
1098
1304
|
}
|
|
1099
1305
|
}));
|
|
1100
|
-
|
|
1306
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_STOP_SCAN, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1101
1307
|
yield stopScanning();
|
|
1102
1308
|
}));
|
|
1103
|
-
|
|
1104
|
-
|
|
1309
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_GET_DEVICE, (_event, deviceId) => getDevice(deviceId));
|
|
1310
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1105
1311
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC CONNECT request received:', {
|
|
1106
1312
|
deviceId,
|
|
1107
1313
|
hasPeripheral: connectedDevices.has(deviceId),
|
|
1108
1314
|
hasCharacteristics: deviceCharacteristics.has(deviceId),
|
|
1109
1315
|
totalConnectedDevices: connectedDevices.size,
|
|
1110
1316
|
});
|
|
1111
|
-
|
|
1317
|
+
clearIdleDisconnect(deviceId);
|
|
1318
|
+
yield awaitIdleDisconnect(deviceId);
|
|
1319
|
+
try {
|
|
1320
|
+
yield connectDevice(deviceId, webContents);
|
|
1321
|
+
}
|
|
1322
|
+
finally {
|
|
1323
|
+
if (connectedDevices.has(deviceId)) {
|
|
1324
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1112
1327
|
}));
|
|
1113
|
-
|
|
1328
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE, (_event, deviceId, keepSession) => {
|
|
1329
|
+
if (!connectedDevices.has(deviceId))
|
|
1330
|
+
return;
|
|
1331
|
+
if (keepSession) {
|
|
1332
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
armIdleDisconnect(deviceId);
|
|
1336
|
+
});
|
|
1337
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1114
1338
|
yield disconnectDevice(deviceId);
|
|
1115
1339
|
}));
|
|
1116
|
-
|
|
1117
|
-
yield transmitHexDataToDevice(deviceId, hexData);
|
|
1340
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData, options) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1341
|
+
yield transmitHexDataToDevice(deviceId, hexData, options);
|
|
1118
1342
|
}));
|
|
1119
|
-
|
|
1343
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1120
1344
|
yield subscribeNotifications(deviceId, (data) => {
|
|
1121
1345
|
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
|
|
1122
1346
|
});
|
|
1347
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1123
1348
|
}));
|
|
1124
|
-
|
|
1349
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1125
1350
|
yield unsubscribeNotifications(deviceId);
|
|
1351
|
+
armIdleDisconnect(deviceId);
|
|
1126
1352
|
}));
|
|
1127
|
-
|
|
1353
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1128
1354
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
1129
1355
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Cancel pairing invoked', {
|
|
1130
1356
|
platform: process.platform,
|
|
@@ -1140,7 +1366,7 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1140
1366
|
}
|
|
1141
1367
|
}
|
|
1142
1368
|
}));
|
|
1143
|
-
|
|
1369
|
+
handle(hdShared.EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1144
1370
|
try {
|
|
1145
1371
|
const bluetoothStatus = yield checkBluetoothAvailability();
|
|
1146
1372
|
safeLog(logger, 'info', 'Bluetooth availability check completed:', bluetoothStatus);
|
|
@@ -1158,23 +1384,19 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1158
1384
|
}));
|
|
1159
1385
|
webContents.on('destroyed', () => {
|
|
1160
1386
|
safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1387
|
+
(() => index.__awaiter(this, void 0, void 0, function* () {
|
|
1388
|
+
const deviceIds = Array.from(connectedDevices.keys());
|
|
1389
|
+
for (const deviceId of deviceIds) {
|
|
1390
|
+
yield unsubscribeNotifications(deviceId).catch(() => undefined);
|
|
1391
|
+
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1392
|
+
}
|
|
1393
|
+
yield stopScanning().catch(() => undefined);
|
|
1394
|
+
cleanupNobleListeners();
|
|
1395
|
+
discoveredDevices.clear();
|
|
1396
|
+
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|
|
1397
|
+
}))().catch(error => {
|
|
1398
|
+
safeLog(logger, 'error', 'Noble BLE cleanup failed', error);
|
|
1169
1399
|
});
|
|
1170
|
-
stopScanning();
|
|
1171
|
-
if (noble && persistentStateListener) {
|
|
1172
|
-
noble.removeListener('stateChange', persistentStateListener);
|
|
1173
|
-
persistentStateListener = null;
|
|
1174
|
-
}
|
|
1175
|
-
cleanupNobleListeners();
|
|
1176
|
-
discoveredDevices.clear();
|
|
1177
|
-
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|
|
1178
1400
|
});
|
|
1179
1401
|
safeLog(logger, 'info', 'Noble BLE IPC handlers setup completed');
|
|
1180
1402
|
}
|
|
@@ -1184,4 +1406,5 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1184
1406
|
}
|
|
1185
1407
|
}
|
|
1186
1408
|
|
|
1409
|
+
exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
|
|
1187
1410
|
exports.setupNobleBleHandlers = setupNobleBleHandlers;
|