@onekeyfe/hd-transport-electron 1.2.0-alpha.13 → 1.2.0-alpha.131
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-fe6d3919.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-c88e98df.js} +456 -231
- 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 +518 -247
- 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-fe6d3919.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);
|
|
@@ -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
|
});
|
|
@@ -306,12 +413,36 @@ function handleDeviceDisconnect(deviceId, webContents) {
|
|
|
306
413
|
});
|
|
307
414
|
}
|
|
308
415
|
function setupDisconnectListener(peripheral, deviceId, webContents) {
|
|
309
|
-
|
|
310
|
-
|
|
416
|
+
const existing = deviceDisconnectListeners.get(deviceId);
|
|
417
|
+
if (existing) {
|
|
418
|
+
existing.peripheral.removeListener('disconnect', existing.listener);
|
|
419
|
+
}
|
|
420
|
+
const listener = () => {
|
|
311
421
|
handleDeviceDisconnect(deviceId, webContents);
|
|
312
|
-
}
|
|
422
|
+
};
|
|
423
|
+
deviceDisconnectListeners.set(deviceId, { peripheral, listener });
|
|
424
|
+
peripheral.on('disconnect', listener);
|
|
425
|
+
setupMtuListener(peripheral, deviceId, webContents);
|
|
313
426
|
}
|
|
314
|
-
function
|
|
427
|
+
function setupMtuListener(peripheral, deviceId, webContents) {
|
|
428
|
+
const existing = deviceMtuListeners.get(deviceId);
|
|
429
|
+
if (existing) {
|
|
430
|
+
existing.peripheral.removeListener('mtu', existing.listener);
|
|
431
|
+
}
|
|
432
|
+
const listener = (mtu) => {
|
|
433
|
+
if (!Number.isFinite(mtu) || mtu <= 0)
|
|
434
|
+
return;
|
|
435
|
+
if (webContents.isDestroyed())
|
|
436
|
+
return;
|
|
437
|
+
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
|
|
438
|
+
id: deviceId,
|
|
439
|
+
mtu,
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
deviceMtuListeners.set(deviceId, { peripheral, listener });
|
|
443
|
+
peripheral.on('mtu', listener);
|
|
444
|
+
}
|
|
445
|
+
function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
|
|
315
446
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
316
447
|
return new Promise((resolve, reject) => {
|
|
317
448
|
writeCharacteristic.write(buffer, true, (error) => {
|
|
@@ -343,7 +474,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
343
474
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Write characteristic not available for ${deviceId}`);
|
|
344
475
|
}
|
|
345
476
|
try {
|
|
346
|
-
yield
|
|
477
|
+
yield writeCharacteristicWithoutResponse(deviceId, latestWrite, payload);
|
|
347
478
|
}
|
|
348
479
|
catch (e) {
|
|
349
480
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
@@ -402,52 +533,57 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
402
533
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `No response observed after ${RETRY_CONFIG.MAX_ATTEMPTS} writes: ${deviceId}`);
|
|
403
534
|
});
|
|
404
535
|
}
|
|
405
|
-
function transmitHexDataToDevice(deviceId, hexData) {
|
|
536
|
+
function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
406
537
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
407
538
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
408
539
|
const peripheral = connectedDevices.get(deviceId);
|
|
409
540
|
if (!peripheral || !characteristics) {
|
|
410
541
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Device ${deviceId} not connected or characteristics not available`);
|
|
411
542
|
}
|
|
543
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
412
544
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
413
545
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
546
|
+
const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
547
|
+
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
414
548
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
415
549
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
416
550
|
if (!writeCharacteristic) {
|
|
417
551
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
418
552
|
}
|
|
419
|
-
if (toBuffer.length <=
|
|
420
|
-
|
|
421
|
-
|
|
553
|
+
if (toBuffer.length <= packetCapacity) {
|
|
554
|
+
if (pacingDelayMs > 0)
|
|
555
|
+
yield hdShared.wait(pacingDelayMs);
|
|
556
|
+
yield writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, toBuffer);
|
|
422
557
|
return;
|
|
423
558
|
}
|
|
424
559
|
for (let offset = 0; offset < toBuffer.length;) {
|
|
425
|
-
const chunkSize = Math.min(
|
|
560
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
426
561
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
427
562
|
offset += chunkSize;
|
|
428
563
|
const latest = doGetWriteCharacteristic();
|
|
429
564
|
if (!latest) {
|
|
430
565
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
431
566
|
}
|
|
432
|
-
yield
|
|
433
|
-
if (offset < toBuffer.length) {
|
|
434
|
-
yield hdShared.wait(
|
|
567
|
+
yield writeCharacteristicWithoutResponse(deviceId, latest, chunk);
|
|
568
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
569
|
+
yield hdShared.wait(pacingDelayMs);
|
|
435
570
|
}
|
|
436
571
|
}
|
|
437
572
|
return;
|
|
438
573
|
}
|
|
439
|
-
if (toBuffer.length <=
|
|
440
|
-
|
|
574
|
+
if (toBuffer.length <= packetCapacity) {
|
|
575
|
+
if (pacingDelayMs > 0)
|
|
576
|
+
yield hdShared.wait(pacingDelayMs);
|
|
441
577
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
|
|
442
578
|
return;
|
|
443
579
|
}
|
|
444
580
|
for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
|
|
445
|
-
const chunkSize = Math.min(
|
|
581
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
446
582
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
447
583
|
offset += chunkSize;
|
|
448
584
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, chunk, `chunk-${idx + 1}`);
|
|
449
|
-
if (offset < toBuffer.length) {
|
|
450
|
-
yield hdShared.wait(
|
|
585
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
586
|
+
yield hdShared.wait(pacingDelayMs);
|
|
451
587
|
}
|
|
452
588
|
}
|
|
453
589
|
});
|
|
@@ -470,17 +606,25 @@ function handleDeviceDiscovered(peripheral) {
|
|
|
470
606
|
function ensureDiscoverListener() {
|
|
471
607
|
if (!noble)
|
|
472
608
|
return;
|
|
473
|
-
|
|
474
|
-
if (listenerCount === 0) {
|
|
609
|
+
if (!persistentDiscoverListener) {
|
|
475
610
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Discover listener missing, re-adding it');
|
|
476
|
-
|
|
611
|
+
persistentDiscoverListener = (peripheral) => {
|
|
477
612
|
handleDeviceDiscovered(peripheral);
|
|
478
|
-
}
|
|
613
|
+
};
|
|
614
|
+
noble.on('discover', persistentDiscoverListener);
|
|
479
615
|
}
|
|
480
616
|
else {
|
|
481
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already
|
|
617
|
+
logger === null || logger === void 0 ? void 0 : logger.debug('[NobleBLE] Discover listener already registered');
|
|
482
618
|
}
|
|
483
619
|
}
|
|
620
|
+
function waitForNobleScanStop(nobleInstance) {
|
|
621
|
+
return index.__awaiter(this, void 0, void 0, function* () {
|
|
622
|
+
yield runBleCallbackOperation(callback => nobleInstance.stopScanning(() => callback()), {
|
|
623
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
624
|
+
timeoutBehavior: 'resolve',
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
484
628
|
function performTargetedScan(targetDeviceId) {
|
|
485
629
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
486
630
|
if (!noble) {
|
|
@@ -489,33 +633,43 @@ function performTargetedScan(targetDeviceId) {
|
|
|
489
633
|
const nobleInstance = noble;
|
|
490
634
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
|
|
491
635
|
return new Promise((resolve, reject) => {
|
|
636
|
+
let settled = false;
|
|
637
|
+
const finish = (peripheral, error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
638
|
+
if (settled)
|
|
639
|
+
return;
|
|
640
|
+
settled = true;
|
|
641
|
+
if (timeoutId)
|
|
642
|
+
clearTimeout(timeoutId);
|
|
643
|
+
nobleInstance.removeListener('discover', onDiscover);
|
|
644
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
645
|
+
if (error) {
|
|
646
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start targeted scan:', error);
|
|
647
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (peripheral) {
|
|
651
|
+
discoveredDevices.set(peripheral.id, peripheral);
|
|
652
|
+
}
|
|
653
|
+
resolve(peripheral);
|
|
654
|
+
});
|
|
492
655
|
const onDiscover = (peripheral) => {
|
|
493
656
|
var _a;
|
|
494
|
-
if (peripheral.id === targetDeviceId) {
|
|
657
|
+
if (peripheral.id === targetDeviceId && isOneKeyPeripheral(peripheral)) {
|
|
495
658
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Target device found during targeted scan:', {
|
|
496
659
|
id: peripheral.id,
|
|
497
660
|
name: (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName,
|
|
498
661
|
});
|
|
499
|
-
|
|
500
|
-
nobleInstance.removeListener('discover', onDiscover);
|
|
501
|
-
nobleInstance.stopScanning();
|
|
502
|
-
discoveredDevices.set(peripheral.id, peripheral);
|
|
503
|
-
resolve(peripheral);
|
|
662
|
+
finish(peripheral).catch(reject);
|
|
504
663
|
}
|
|
505
664
|
};
|
|
506
665
|
const timeoutId = setTimeout(() => {
|
|
507
|
-
nobleInstance.removeListener('discover', onDiscover);
|
|
508
|
-
nobleInstance.stopScanning();
|
|
509
666
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
|
|
510
|
-
|
|
511
|
-
},
|
|
667
|
+
finish(null).catch(reject);
|
|
668
|
+
}, NOBLE_BLE_TARGETED_SCAN_TIMEOUT_MS);
|
|
512
669
|
nobleInstance.on('discover', onDiscover);
|
|
513
|
-
nobleInstance.startScanning([],
|
|
670
|
+
nobleInstance.startScanning([], true, (error) => {
|
|
514
671
|
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));
|
|
672
|
+
finish(null, error).catch(reject);
|
|
519
673
|
return;
|
|
520
674
|
}
|
|
521
675
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
|
|
@@ -538,44 +692,44 @@ function enumerateDevices() {
|
|
|
538
692
|
return new Promise((resolve, reject) => {
|
|
539
693
|
const devices = [];
|
|
540
694
|
let intervalId;
|
|
541
|
-
const cleanup = () => {
|
|
695
|
+
const cleanup = () => index.__awaiter(this, void 0, void 0, function* () {
|
|
542
696
|
clearTimeout(timeoutId);
|
|
543
697
|
if (intervalId)
|
|
544
698
|
clearInterval(intervalId);
|
|
545
|
-
nobleInstance
|
|
699
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
700
|
+
});
|
|
701
|
+
const pushDevice = (peripheral, id) => {
|
|
702
|
+
var _a;
|
|
703
|
+
if (devices.some(d => d.id === id))
|
|
704
|
+
return;
|
|
705
|
+
devices.push({
|
|
706
|
+
commType: 'electron-ble',
|
|
707
|
+
id,
|
|
708
|
+
name: ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device',
|
|
709
|
+
state: peripheral.state || 'disconnected',
|
|
710
|
+
});
|
|
546
711
|
};
|
|
547
712
|
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
|
-
});
|
|
713
|
+
discoveredDevices.forEach(pushDevice);
|
|
714
|
+
connectedDevices.forEach(pushDevice);
|
|
561
715
|
};
|
|
562
|
-
const timeoutId = setTimeout(() => {
|
|
716
|
+
const timeoutId = setTimeout(() => index.__awaiter(this, void 0, void 0, function* () {
|
|
563
717
|
checkDevices();
|
|
564
|
-
cleanup();
|
|
718
|
+
yield cleanup();
|
|
565
719
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scan completed, found devices:', devices.length);
|
|
566
720
|
resolve(devices);
|
|
567
|
-
}, DEVICE_SCAN_TIMEOUT);
|
|
721
|
+
}), DEVICE_SCAN_TIMEOUT);
|
|
568
722
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning for OneKey BLE devices');
|
|
569
|
-
nobleInstance.startScanning([],
|
|
723
|
+
nobleInstance.startScanning([], true, (error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
570
724
|
if (error) {
|
|
571
|
-
cleanup();
|
|
725
|
+
yield cleanup();
|
|
572
726
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to start scanning:', error);
|
|
573
727
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, error.message));
|
|
574
728
|
return;
|
|
575
729
|
}
|
|
576
730
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning started for OneKey devices');
|
|
577
731
|
intervalId = setInterval(checkDevices, DEVICE_CHECK_INTERVAL);
|
|
578
|
-
});
|
|
732
|
+
}));
|
|
579
733
|
});
|
|
580
734
|
});
|
|
581
735
|
}
|
|
@@ -583,25 +737,20 @@ function stopScanning() {
|
|
|
583
737
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
584
738
|
if (!noble)
|
|
585
739
|
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
|
-
});
|
|
740
|
+
const nobleInstance = noble;
|
|
741
|
+
yield waitForNobleScanStop(nobleInstance);
|
|
742
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Scanning stopped');
|
|
596
743
|
});
|
|
597
744
|
}
|
|
598
745
|
function cleanupNobleListeners() {
|
|
599
746
|
if (!noble)
|
|
600
747
|
return;
|
|
601
748
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
749
|
+
if (persistentDiscoverListener) {
|
|
750
|
+
noble.removeListener('discover', persistentDiscoverListener);
|
|
751
|
+
persistentDiscoverListener = null;
|
|
752
|
+
}
|
|
753
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Owned Noble listeners cleaned up');
|
|
605
754
|
}
|
|
606
755
|
catch (error) {
|
|
607
756
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Failed to clean up some listeners:', error);
|
|
@@ -612,22 +761,12 @@ function getDevice(deviceId) {
|
|
|
612
761
|
const peripheral = discoveredDevices.get(deviceId);
|
|
613
762
|
if (peripheral) {
|
|
614
763
|
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
|
-
};
|
|
764
|
+
return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}));
|
|
621
765
|
}
|
|
622
766
|
const connectedPeripheral = connectedDevices.get(deviceId);
|
|
623
767
|
if (connectedPeripheral) {
|
|
624
768
|
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
|
-
};
|
|
769
|
+
return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}));
|
|
631
770
|
}
|
|
632
771
|
return {
|
|
633
772
|
commType: 'electron-ble',
|
|
@@ -678,14 +817,7 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
678
817
|
deviceId: peripheral.id,
|
|
679
818
|
serviceUUIDs: services.map(service => service.uuid),
|
|
680
819
|
});
|
|
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
|
-
}
|
|
820
|
+
const service = services.find(s => hdShared.matchesKnownBleUuid(s.uuid, ONEKEY_SERVICE_UUID_ALIASES));
|
|
689
821
|
if (!service) {
|
|
690
822
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
|
|
691
823
|
}
|
|
@@ -708,12 +840,10 @@ function discoverServicesAndCharacteristics(peripheral) {
|
|
|
708
840
|
let writeCharacteristic = null;
|
|
709
841
|
let notifyCharacteristic = null;
|
|
710
842
|
for (const characteristic of characteristics) {
|
|
711
|
-
|
|
712
|
-
const uuidKey = uuid.length >= 8 ? uuid.substring(4, 8) : uuid;
|
|
713
|
-
if (uuidKey === NORMALIZED_WRITE_UUID) {
|
|
843
|
+
if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_WRITE_UUID_ALIASES)) {
|
|
714
844
|
writeCharacteristic = characteristic;
|
|
715
845
|
}
|
|
716
|
-
else if (
|
|
846
|
+
else if (hdShared.matchesKnownBleUuid(characteristic.uuid, ONEKEY_NOTIFY_UUID_ALIASES)) {
|
|
717
847
|
notifyCharacteristic = characteristic;
|
|
718
848
|
}
|
|
719
849
|
}
|
|
@@ -747,27 +877,17 @@ function forceReconnectPeripheral(peripheral, deviceId) {
|
|
|
747
877
|
reason: 'force-reconnect',
|
|
748
878
|
});
|
|
749
879
|
if (peripheral.state === 'connected') {
|
|
750
|
-
peripheral.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
resolve();
|
|
755
|
-
});
|
|
756
|
-
});
|
|
880
|
+
yield runBleCallbackOperation(callback => peripheral.disconnect(() => {
|
|
881
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force disconnect completed');
|
|
882
|
+
callback();
|
|
883
|
+
}), { timeoutMs: BLE_CLEANUP_TIMEOUT, timeoutBehavior: 'resolve' });
|
|
757
884
|
}
|
|
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
|
-
});
|
|
885
|
+
yield runBleCallbackOperation(callback => peripheral.connect(callback), {
|
|
886
|
+
timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
|
|
887
|
+
timeoutBehavior: 'reject',
|
|
770
888
|
});
|
|
889
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Force reconnect successful');
|
|
890
|
+
connectedDevices.set(deviceId, peripheral);
|
|
771
891
|
yield hdShared.wait(500);
|
|
772
892
|
});
|
|
773
893
|
}
|
|
@@ -844,18 +964,83 @@ function discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId) {
|
|
|
844
964
|
}
|
|
845
965
|
function setupConnectionAndDiscoverServices(peripheral, deviceId, webContents) {
|
|
846
966
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
967
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
968
|
+
try {
|
|
969
|
+
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
970
|
+
}
|
|
971
|
+
catch (initialError) {
|
|
972
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct service discovery failed, retrying after reconnect', {
|
|
973
|
+
deviceId,
|
|
974
|
+
error: initialError,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
847
977
|
yield forceReconnectPeripheral(peripheral, deviceId);
|
|
848
978
|
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
849
979
|
try {
|
|
850
980
|
return yield discoverServicesAndCharacteristicsWithRetry(peripheral, deviceId);
|
|
851
981
|
}
|
|
852
|
-
catch (
|
|
853
|
-
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed, attempting fresh scan...',
|
|
982
|
+
catch (reconnectError) {
|
|
983
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Service discovery failed after reconnect, attempting fresh scan...', reconnectError);
|
|
854
984
|
return freshScanAndDiscover(deviceId, webContents);
|
|
855
985
|
}
|
|
856
986
|
});
|
|
857
987
|
}
|
|
988
|
+
const DIRECT_CONNECT_TIMEOUT_MS = 2000;
|
|
989
|
+
const DIRECT_CONNECT_COOLDOWN_MS = 15000;
|
|
990
|
+
const directConnectCooldownUntil = new Map();
|
|
991
|
+
function tryDirectConnectById(deviceId) {
|
|
992
|
+
var _a;
|
|
993
|
+
return index.__awaiter(this, void 0, void 0, function* () {
|
|
994
|
+
const nobleInstance = noble;
|
|
995
|
+
if (!(nobleInstance === null || nobleInstance === void 0 ? void 0 : nobleInstance.connectAsync))
|
|
996
|
+
return undefined;
|
|
997
|
+
const cooldownUntil = (_a = directConnectCooldownUntil.get(deviceId)) !== null && _a !== void 0 ? _a : 0;
|
|
998
|
+
if (Date.now() < cooldownUntil)
|
|
999
|
+
return undefined;
|
|
1000
|
+
try {
|
|
1001
|
+
const directPromise = nobleInstance.connectAsync(deviceId);
|
|
1002
|
+
const raced = yield Promise.race([
|
|
1003
|
+
directPromise,
|
|
1004
|
+
new Promise(resolve => {
|
|
1005
|
+
setTimeout(() => resolve('timeout'), DIRECT_CONNECT_TIMEOUT_MS);
|
|
1006
|
+
}),
|
|
1007
|
+
]);
|
|
1008
|
+
if (raced === 'timeout') {
|
|
1009
|
+
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1010
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id timed out, falling back to scan', {
|
|
1011
|
+
deviceId,
|
|
1012
|
+
});
|
|
1013
|
+
directPromise
|
|
1014
|
+
.then(late => {
|
|
1015
|
+
const latePeripheral = late !== null && late !== void 0 ? late : discoveredDevices.get(deviceId);
|
|
1016
|
+
if (latePeripheral &&
|
|
1017
|
+
latePeripheral.state === 'connected' &&
|
|
1018
|
+
!connectedDevices.has(deviceId)) {
|
|
1019
|
+
latePeripheral.removeAllListeners('disconnect');
|
|
1020
|
+
latePeripheral.disconnect(() => undefined);
|
|
1021
|
+
}
|
|
1022
|
+
})
|
|
1023
|
+
.catch(() => undefined);
|
|
1024
|
+
return undefined;
|
|
1025
|
+
}
|
|
1026
|
+
const peripheral = raced !== null && raced !== void 0 ? raced : discoveredDevices.get(deviceId);
|
|
1027
|
+
if (!peripheral || peripheral.state !== 'connected')
|
|
1028
|
+
return undefined;
|
|
1029
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id succeeded', { deviceId });
|
|
1030
|
+
return peripheral;
|
|
1031
|
+
}
|
|
1032
|
+
catch (error) {
|
|
1033
|
+
directConnectCooldownUntil.set(deviceId, Date.now() + DIRECT_CONNECT_COOLDOWN_MS);
|
|
1034
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Direct connect-by-id failed, falling back to scan', {
|
|
1035
|
+
deviceId,
|
|
1036
|
+
error: String(error),
|
|
1037
|
+
});
|
|
1038
|
+
return undefined;
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
858
1042
|
function connectDevice(deviceId, webContents) {
|
|
1043
|
+
var _a;
|
|
859
1044
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
860
1045
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Connect device request:', {
|
|
861
1046
|
deviceId,
|
|
@@ -865,7 +1050,7 @@ function connectDevice(deviceId, webContents) {
|
|
|
865
1050
|
totalDiscovered: discoveredDevices.size,
|
|
866
1051
|
totalConnected: connectedDevices.size,
|
|
867
1052
|
});
|
|
868
|
-
let peripheral = discoveredDevices.get(deviceId);
|
|
1053
|
+
let peripheral = (_a = discoveredDevices.get(deviceId)) !== null && _a !== void 0 ? _a : connectedDevices.get(deviceId);
|
|
869
1054
|
if (!peripheral) {
|
|
870
1055
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device not discovered, attempting targeted scan for:', deviceId);
|
|
871
1056
|
if (!noble) {
|
|
@@ -874,6 +1059,12 @@ function connectDevice(deviceId, webContents) {
|
|
|
874
1059
|
if (!noble) {
|
|
875
1060
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble not available');
|
|
876
1061
|
}
|
|
1062
|
+
peripheral = yield tryDirectConnectById(deviceId);
|
|
1063
|
+
if (peripheral) {
|
|
1064
|
+
discoveredDevices.set(deviceId, peripheral);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (!peripheral) {
|
|
877
1068
|
try {
|
|
878
1069
|
const foundPeripheral = yield performTargetedScan(deviceId);
|
|
879
1070
|
if (!foundPeripheral) {
|
|
@@ -894,8 +1085,8 @@ function connectDevice(deviceId, webContents) {
|
|
|
894
1085
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device already connected, skipping connection step');
|
|
895
1086
|
if (!connectedDevices.has(deviceId)) {
|
|
896
1087
|
connectedDevices.set(deviceId, peripheral);
|
|
897
|
-
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
898
1088
|
}
|
|
1089
|
+
setupDisconnectListener(peripheral, deviceId, webContents);
|
|
899
1090
|
if (deviceCharacteristics.has(deviceId)) {
|
|
900
1091
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device characteristics already available');
|
|
901
1092
|
const ongoingOperation = subscriptionOperations.get(deviceId);
|
|
@@ -917,18 +1108,37 @@ function connectDevice(deviceId, webContents) {
|
|
|
917
1108
|
notificationCallbacks.delete(deviceId);
|
|
918
1109
|
subscribedDevices.delete(deviceId);
|
|
919
1110
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1111
|
+
try {
|
|
1112
|
+
const characteristics = yield setupConnectionAndDiscoverServices(peripheral, deviceId, webContents);
|
|
1113
|
+
deviceCharacteristics.set(deviceId, characteristics);
|
|
1114
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Device ready for communication:', deviceId);
|
|
1115
|
+
}
|
|
1116
|
+
catch (setupError) {
|
|
1117
|
+
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection setup failed on kept-alive link:', setupError);
|
|
1118
|
+
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1119
|
+
throw setupError;
|
|
1120
|
+
}
|
|
923
1121
|
return;
|
|
924
1122
|
}
|
|
925
1123
|
return new Promise((resolve, reject) => {
|
|
1124
|
+
let connectionTimedOut = false;
|
|
926
1125
|
const timeout = setTimeout(() => {
|
|
1126
|
+
connectionTimedOut = true;
|
|
927
1127
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
|
|
928
|
-
},
|
|
1128
|
+
}, NOBLE_BLE_CONNECTION_TIMEOUT_MS);
|
|
929
1129
|
const connectedPeripheral = peripheral;
|
|
930
1130
|
connectedPeripheral.connect((error) => index.__awaiter(this, void 0, void 0, function* () {
|
|
931
1131
|
clearTimeout(timeout);
|
|
1132
|
+
if (connectionTimedOut) {
|
|
1133
|
+
if (!error) {
|
|
1134
|
+
try {
|
|
1135
|
+
connectedPeripheral.disconnect(() => undefined);
|
|
1136
|
+
}
|
|
1137
|
+
catch (_a) {
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
932
1142
|
if (error) {
|
|
933
1143
|
logger === null || logger === void 0 ? void 0 : logger.error('[NobleBLE] Connection failed:', error);
|
|
934
1144
|
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, error.message));
|
|
@@ -958,17 +1168,20 @@ function disconnectDevice(deviceId) {
|
|
|
958
1168
|
if (!peripheral) {
|
|
959
1169
|
return;
|
|
960
1170
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
peripheral.disconnect
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1171
|
+
const disconnectEntry = deviceDisconnectListeners.get(deviceId);
|
|
1172
|
+
if (disconnectEntry) {
|
|
1173
|
+
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
1174
|
+
deviceDisconnectListeners.delete(deviceId);
|
|
1175
|
+
}
|
|
1176
|
+
yield runBleCallbackOperation(callback => peripheral.disconnect(() => callback()), {
|
|
1177
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1178
|
+
timeoutBehavior: 'resolve',
|
|
1179
|
+
});
|
|
1180
|
+
cleanupDevice(deviceId, undefined, {
|
|
1181
|
+
cleanupConnection: true,
|
|
1182
|
+
sendDisconnectEvent: false,
|
|
1183
|
+
cancelOperations: true,
|
|
1184
|
+
reason: 'manual-disconnect',
|
|
972
1185
|
});
|
|
973
1186
|
});
|
|
974
1187
|
}
|
|
@@ -983,18 +1196,11 @@ function unsubscribeNotifications(deviceId) {
|
|
|
983
1196
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Unsubscribing from notifications for device:', deviceId);
|
|
984
1197
|
subscriptionOperations.set(deviceId, 'unsubscribing');
|
|
985
1198
|
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
|
-
});
|
|
1199
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
|
|
1200
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1201
|
+
timeoutBehavior: 'resolve',
|
|
997
1202
|
});
|
|
1203
|
+
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Notification unsubscription completed');
|
|
998
1204
|
notifyCharacteristic.removeAllListeners('data');
|
|
999
1205
|
notificationCallbacks.delete(deviceId);
|
|
1000
1206
|
subscribedDevices.delete(deviceId);
|
|
@@ -1046,19 +1252,13 @@ function subscribeNotifications(deviceId, callback) {
|
|
|
1046
1252
|
notificationCallbacks.set(deviceId, callback);
|
|
1047
1253
|
function rebuildAppSubscription(deviceId, notifyCharacteristic) {
|
|
1048
1254
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
1049
|
-
yield
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
});
|
|
1255
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.unsubscribe(callback), {
|
|
1256
|
+
timeoutMs: BLE_CLEANUP_TIMEOUT,
|
|
1257
|
+
timeoutBehavior: 'resolve',
|
|
1053
1258
|
});
|
|
1054
|
-
yield
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
reject(error);
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
resolve();
|
|
1061
|
-
});
|
|
1259
|
+
yield runBleCallbackOperation(callback => notifyCharacteristic.subscribe(callback), {
|
|
1260
|
+
timeoutMs: NOBLE_BLE_CONNECTION_TIMEOUT_MS,
|
|
1261
|
+
timeoutBehavior: 'reject',
|
|
1062
1262
|
});
|
|
1063
1263
|
notifyCharacteristic.on('data', (data) => {
|
|
1064
1264
|
if (!pairedDevices.has(deviceId)) {
|
|
@@ -1082,8 +1282,12 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1082
1282
|
try {
|
|
1083
1283
|
logger = require('electron-log');
|
|
1084
1284
|
const { ipcMain } = require('electron');
|
|
1285
|
+
const handle = (channel, listener) => {
|
|
1286
|
+
ipcMain.removeHandler(channel);
|
|
1287
|
+
ipcMain.handle(channel, listener);
|
|
1288
|
+
};
|
|
1085
1289
|
safeLog(logger, 'info', 'Setting up Noble BLE IPC handlers');
|
|
1086
|
-
|
|
1290
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1087
1291
|
try {
|
|
1088
1292
|
const devices = yield enumerateDevices();
|
|
1089
1293
|
safeLog(logger, 'debug', 'Enumeration completed', {
|
|
@@ -1097,34 +1301,54 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1097
1301
|
throw error;
|
|
1098
1302
|
}
|
|
1099
1303
|
}));
|
|
1100
|
-
|
|
1304
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_STOP_SCAN, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1101
1305
|
yield stopScanning();
|
|
1102
1306
|
}));
|
|
1103
|
-
|
|
1104
|
-
|
|
1307
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_GET_DEVICE, (_event, deviceId) => getDevice(deviceId));
|
|
1308
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1105
1309
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] IPC CONNECT request received:', {
|
|
1106
1310
|
deviceId,
|
|
1107
1311
|
hasPeripheral: connectedDevices.has(deviceId),
|
|
1108
1312
|
hasCharacteristics: deviceCharacteristics.has(deviceId),
|
|
1109
1313
|
totalConnectedDevices: connectedDevices.size,
|
|
1110
1314
|
});
|
|
1111
|
-
|
|
1315
|
+
clearIdleDisconnect(deviceId);
|
|
1316
|
+
yield awaitIdleDisconnect(deviceId);
|
|
1317
|
+
try {
|
|
1318
|
+
yield connectDevice(deviceId, webContents);
|
|
1319
|
+
}
|
|
1320
|
+
finally {
|
|
1321
|
+
if (connectedDevices.has(deviceId)) {
|
|
1322
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1112
1325
|
}));
|
|
1113
|
-
|
|
1326
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_RELEASE, (_event, deviceId, keepSession) => {
|
|
1327
|
+
if (!connectedDevices.has(deviceId))
|
|
1328
|
+
return;
|
|
1329
|
+
if (keepSession) {
|
|
1330
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
armIdleDisconnect(deviceId);
|
|
1334
|
+
});
|
|
1335
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1114
1336
|
yield disconnectDevice(deviceId);
|
|
1115
1337
|
}));
|
|
1116
|
-
|
|
1117
|
-
yield transmitHexDataToDevice(deviceId, hexData);
|
|
1338
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData, options) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1339
|
+
yield transmitHexDataToDevice(deviceId, hexData, options);
|
|
1118
1340
|
}));
|
|
1119
|
-
|
|
1341
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1120
1342
|
yield subscribeNotifications(deviceId, (data) => {
|
|
1121
1343
|
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_NOTIFICATION, deviceId, data);
|
|
1122
1344
|
});
|
|
1345
|
+
armIdleDisconnect(deviceId, BLE_BUSY_BACKSTOP_MS, 'busy-backstop');
|
|
1123
1346
|
}));
|
|
1124
|
-
|
|
1347
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_UNSUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1125
1348
|
yield unsubscribeNotifications(deviceId);
|
|
1349
|
+
armIdleDisconnect(deviceId);
|
|
1126
1350
|
}));
|
|
1127
|
-
|
|
1351
|
+
handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_CANCEL_PAIRING, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1128
1352
|
const deviceIds = Array.from(connectedDevices.keys());
|
|
1129
1353
|
logger === null || logger === void 0 ? void 0 : logger.info('[NobleBLE] Cancel pairing invoked', {
|
|
1130
1354
|
platform: process.platform,
|
|
@@ -1140,7 +1364,7 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1140
1364
|
}
|
|
1141
1365
|
}
|
|
1142
1366
|
}));
|
|
1143
|
-
|
|
1367
|
+
handle(hdShared.EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK, () => index.__awaiter(this, void 0, void 0, function* () {
|
|
1144
1368
|
try {
|
|
1145
1369
|
const bluetoothStatus = yield checkBluetoothAvailability();
|
|
1146
1370
|
safeLog(logger, 'info', 'Bluetooth availability check completed:', bluetoothStatus);
|
|
@@ -1158,23 +1382,23 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1158
1382
|
}));
|
|
1159
1383
|
webContents.on('destroyed', () => {
|
|
1160
1384
|
safeLog(logger, 'info', 'Cleaning up Noble BLE handlers');
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1385
|
+
(() => index.__awaiter(this, void 0, void 0, function* () {
|
|
1386
|
+
const deviceIds = Array.from(connectedDevices.keys());
|
|
1387
|
+
for (const deviceId of deviceIds) {
|
|
1388
|
+
yield unsubscribeNotifications(deviceId).catch(() => undefined);
|
|
1389
|
+
yield disconnectDevice(deviceId).catch(() => undefined);
|
|
1390
|
+
}
|
|
1391
|
+
yield stopScanning().catch(() => undefined);
|
|
1392
|
+
if (noble && persistentStateListener) {
|
|
1393
|
+
noble.removeListener('stateChange', persistentStateListener);
|
|
1394
|
+
persistentStateListener = null;
|
|
1395
|
+
}
|
|
1396
|
+
cleanupNobleListeners();
|
|
1397
|
+
discoveredDevices.clear();
|
|
1398
|
+
safeLog(logger, 'info', 'Noble BLE cleanup completed');
|
|
1399
|
+
}))().catch(error => {
|
|
1400
|
+
safeLog(logger, 'error', 'Noble BLE cleanup failed', error);
|
|
1169
1401
|
});
|
|
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
1402
|
});
|
|
1179
1403
|
safeLog(logger, 'info', 'Noble BLE IPC handlers setup completed');
|
|
1180
1404
|
}
|
|
@@ -1184,4 +1408,5 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1184
1408
|
}
|
|
1185
1409
|
}
|
|
1186
1410
|
|
|
1411
|
+
exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
|
|
1187
1412
|
exports.setupNobleBleHandlers = setupNobleBleHandlers;
|