@onekeyfe/hd-transport-electron 1.2.0-alpha.70 → 1.2.0-alpha.72
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-packet-capacity.d.ts +2 -0
- package/dist/ble-packet-capacity.d.ts.map +1 -0
- package/dist/{index-84fea839.js → index-4f7dc0a5.js} +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{noble-ble-handler-a0b2f06d.js → noble-ble-handler-3bd9cd3e.js} +64 -32
- package/dist/noble-ble-handler.d.ts +2 -0
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/types/desktop-api.d.ts +9 -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-packet-capacity.test.ts +13 -0
- package/src/__tests__/noble-ble-handler.test.ts +8 -0
- package/src/ble-packet-capacity.ts +13 -0
- package/src/index.ts +1 -1
- package/src/noble-ble-handler.ts +76 -19
- package/src/types/desktop-api.ts +7 -2
- package/src/types/noble-extended.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ble-packet-capacity.d.ts","sourceRoot":"","sources":["../src/ble-packet-capacity.ts"],"names":[],"mappings":"AAEA,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,qBAAqB,EAAE,MAAM,EAC7B,sBAAsB,EAAE,MAAM,GAC7B,MAAM,CAMR"}
|
|
@@ -32,7 +32,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
32
32
|
|
|
33
33
|
function initNobleBleSupport(webContents) {
|
|
34
34
|
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-
|
|
35
|
+
const { setupNobleBleHandlers } = yield Promise.resolve().then(function () { return require('./noble-ble-handler-3bd9cd3e.js'); });
|
|
36
36
|
setupNobleBleHandlers(webContents);
|
|
37
37
|
});
|
|
38
38
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
|
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
8
8
|
state: string;
|
|
9
|
+
mtu?: number;
|
|
9
10
|
}
|
|
10
11
|
interface CharacteristicPair {
|
|
11
12
|
write: Characteristic;
|
|
@@ -30,6 +31,9 @@ interface Logger {
|
|
|
30
31
|
* Desktop API types for Electron preload script
|
|
31
32
|
* These types define the core interface for Noble BLE communication
|
|
32
33
|
*/
|
|
34
|
+
interface NobleBleWriteOptions {
|
|
35
|
+
pacingDelayMs?: number;
|
|
36
|
+
}
|
|
33
37
|
interface NobleBleAPI {
|
|
34
38
|
enumerate: () => Promise<{
|
|
35
39
|
id: string;
|
|
@@ -38,13 +42,18 @@ interface NobleBleAPI {
|
|
|
38
42
|
getDevice: (uuid: string) => Promise<{
|
|
39
43
|
id: string;
|
|
40
44
|
name: string;
|
|
45
|
+
mtu?: number;
|
|
41
46
|
} | null>;
|
|
42
47
|
connect: (uuid: string) => Promise<void>;
|
|
43
48
|
disconnect: (uuid: string) => Promise<void>;
|
|
44
49
|
subscribe: (uuid: string) => Promise<void>;
|
|
45
50
|
unsubscribe: (uuid: string) => Promise<void>;
|
|
46
|
-
write: (uuid: string, data: string) => Promise<void>;
|
|
51
|
+
write: (uuid: string, data: string, options?: NobleBleWriteOptions) => Promise<void>;
|
|
47
52
|
onNotification: (callback: (deviceId: string, data: string) => void) => () => void;
|
|
53
|
+
onMtuChanged?: (callback: (device: {
|
|
54
|
+
id: string;
|
|
55
|
+
mtu: number;
|
|
56
|
+
}) => void) => () => void;
|
|
48
57
|
onDeviceDisconnected: (callback: (device: {
|
|
49
58
|
id: string;
|
|
50
59
|
name: string;
|
|
@@ -62,4 +71,4 @@ interface DesktopAPI {
|
|
|
62
71
|
|
|
63
72
|
declare function initNobleBleSupport(webContents: WebContents): Promise<void>;
|
|
64
73
|
|
|
65
|
-
export { CharacteristicPair, DesktopAPI, DeviceInfo, Logger, NobleBleAPI, NobleModule, initNobleBleSupport };
|
|
74
|
+
export { CharacteristicPair, DesktopAPI, DeviceInfo, Logger, NobleBleAPI, NobleBleWriteOptions, NobleModule, initNobleBleSupport };
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,cAAc,SAAS,CAAC;AAGxB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,cAAc,SAAS,CAAC;AAGxB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,WAAW,iBAGjE"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-4f7dc0a5.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);
|
|
@@ -98,6 +106,7 @@ const notificationCallbacks = new Map();
|
|
|
98
106
|
const subscribedDevices = new Map();
|
|
99
107
|
const subscriptionOperations = new Map();
|
|
100
108
|
const deviceDisconnectListeners = new Map();
|
|
109
|
+
const deviceMtuListeners = new Map();
|
|
101
110
|
const ONEKEY_SERVICE_UUIDS = [hdShared.ONEKEY_SERVICE_UUID];
|
|
102
111
|
const ONEKEY_SERVICE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_SERVICE_UUID);
|
|
103
112
|
const ONEKEY_WRITE_UUID_ALIASES = hdShared.createKnownBleUuidAliases(hdShared.ONEKEY_WRITE_CHARACTERISTIC_UUID);
|
|
@@ -107,13 +116,19 @@ const DEVICE_SCAN_TIMEOUT = 5000;
|
|
|
107
116
|
const DEVICE_CHECK_INTERVAL = 500;
|
|
108
117
|
const SERVICE_DISCOVERY_TIMEOUT = 10000;
|
|
109
118
|
const BLE_CLEANUP_TIMEOUT = 250;
|
|
110
|
-
const
|
|
111
|
-
const
|
|
119
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
120
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
121
|
+
const DEFAULT_WRITE_PACING_DELAY_MS = 5;
|
|
112
122
|
const RETRY_CONFIG = { MAX_ATTEMPTS: 15, WRITE_TIMEOUT: 2000 };
|
|
113
123
|
const IS_WINDOWS = process.platform === 'win32';
|
|
114
124
|
const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
115
125
|
/status:\s*3/i,
|
|
116
126
|
];
|
|
127
|
+
function resolveNobleBleWritePacingDelay(options) {
|
|
128
|
+
return typeof (options === null || options === void 0 ? void 0 : options.pacingDelayMs) === 'number' && Number.isFinite(options.pacingDelayMs)
|
|
129
|
+
? Math.min(Math.max(Math.floor(options.pacingDelayMs), 0), 1000)
|
|
130
|
+
: DEFAULT_WRITE_PACING_DELAY_MS;
|
|
131
|
+
}
|
|
117
132
|
function isOneKeyPeripheral(peripheral) {
|
|
118
133
|
var _a, _b;
|
|
119
134
|
const serviceUuids = (_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.serviceUuids;
|
|
@@ -300,6 +315,11 @@ function cleanupDevice(deviceId, webContents, options = {}) {
|
|
|
300
315
|
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
301
316
|
deviceDisconnectListeners.delete(deviceId);
|
|
302
317
|
}
|
|
318
|
+
const mtuEntry = deviceMtuListeners.get(deviceId);
|
|
319
|
+
if (mtuEntry) {
|
|
320
|
+
mtuEntry.peripheral.removeListener('mtu', mtuEntry.listener);
|
|
321
|
+
deviceMtuListeners.delete(deviceId);
|
|
322
|
+
}
|
|
303
323
|
connectedDevices.delete(deviceId);
|
|
304
324
|
deviceCharacteristics.delete(deviceId);
|
|
305
325
|
notificationCallbacks.delete(deviceId);
|
|
@@ -343,8 +363,25 @@ function setupDisconnectListener(peripheral, deviceId, webContents) {
|
|
|
343
363
|
};
|
|
344
364
|
deviceDisconnectListeners.set(deviceId, { peripheral, listener });
|
|
345
365
|
peripheral.on('disconnect', listener);
|
|
366
|
+
setupMtuListener(peripheral, deviceId, webContents);
|
|
346
367
|
}
|
|
347
|
-
function
|
|
368
|
+
function setupMtuListener(peripheral, deviceId, webContents) {
|
|
369
|
+
const existing = deviceMtuListeners.get(deviceId);
|
|
370
|
+
if (existing) {
|
|
371
|
+
existing.peripheral.removeListener('mtu', existing.listener);
|
|
372
|
+
}
|
|
373
|
+
const listener = (mtu) => {
|
|
374
|
+
if (!Number.isFinite(mtu) || mtu <= 0)
|
|
375
|
+
return;
|
|
376
|
+
webContents.send(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
|
|
377
|
+
id: deviceId,
|
|
378
|
+
mtu,
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
deviceMtuListeners.set(deviceId, { peripheral, listener });
|
|
382
|
+
peripheral.on('mtu', listener);
|
|
383
|
+
}
|
|
384
|
+
function writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, buffer) {
|
|
348
385
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
349
386
|
return new Promise((resolve, reject) => {
|
|
350
387
|
writeCharacteristic.write(buffer, true, (error) => {
|
|
@@ -376,7 +413,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
376
413
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Write characteristic not available for ${deviceId}`);
|
|
377
414
|
}
|
|
378
415
|
try {
|
|
379
|
-
yield
|
|
416
|
+
yield writeCharacteristicWithoutResponse(deviceId, latestWrite, payload);
|
|
380
417
|
}
|
|
381
418
|
catch (e) {
|
|
382
419
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
@@ -435,7 +472,7 @@ function attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, payl
|
|
|
435
472
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `No response observed after ${RETRY_CONFIG.MAX_ATTEMPTS} writes: ${deviceId}`);
|
|
436
473
|
});
|
|
437
474
|
}
|
|
438
|
-
function transmitHexDataToDevice(deviceId, hexData) {
|
|
475
|
+
function transmitHexDataToDevice(deviceId, hexData, options) {
|
|
439
476
|
return index.__awaiter(this, void 0, void 0, function* () {
|
|
440
477
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
441
478
|
const peripheral = connectedDevices.get(deviceId);
|
|
@@ -444,43 +481,47 @@ function transmitHexDataToDevice(deviceId, hexData) {
|
|
|
444
481
|
}
|
|
445
482
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
446
483
|
const doGetWriteCharacteristic = () => { var _a; return (_a = deviceCharacteristics.get(deviceId)) === null || _a === void 0 ? void 0 : _a.write; };
|
|
484
|
+
const packetCapacity = resolveBlePacketCapacity(peripheral.mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
485
|
+
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
447
486
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
448
487
|
const writeCharacteristic = doGetWriteCharacteristic();
|
|
449
488
|
if (!writeCharacteristic) {
|
|
450
489
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
451
490
|
}
|
|
452
|
-
if (toBuffer.length <=
|
|
453
|
-
|
|
454
|
-
|
|
491
|
+
if (toBuffer.length <= packetCapacity) {
|
|
492
|
+
if (pacingDelayMs > 0)
|
|
493
|
+
yield hdShared.wait(pacingDelayMs);
|
|
494
|
+
yield writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, toBuffer);
|
|
455
495
|
return;
|
|
456
496
|
}
|
|
457
497
|
for (let offset = 0; offset < toBuffer.length;) {
|
|
458
|
-
const chunkSize = Math.min(
|
|
498
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
459
499
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
460
500
|
offset += chunkSize;
|
|
461
501
|
const latest = doGetWriteCharacteristic();
|
|
462
502
|
if (!latest) {
|
|
463
503
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound, `Write characteristic not available for ${deviceId}`);
|
|
464
504
|
}
|
|
465
|
-
yield
|
|
466
|
-
if (offset < toBuffer.length) {
|
|
467
|
-
yield hdShared.wait(
|
|
505
|
+
yield writeCharacteristicWithoutResponse(deviceId, latest, chunk);
|
|
506
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
507
|
+
yield hdShared.wait(pacingDelayMs);
|
|
468
508
|
}
|
|
469
509
|
}
|
|
470
510
|
return;
|
|
471
511
|
}
|
|
472
|
-
if (toBuffer.length <=
|
|
473
|
-
|
|
512
|
+
if (toBuffer.length <= packetCapacity) {
|
|
513
|
+
if (pacingDelayMs > 0)
|
|
514
|
+
yield hdShared.wait(pacingDelayMs);
|
|
474
515
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
|
|
475
516
|
return;
|
|
476
517
|
}
|
|
477
518
|
for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
|
|
478
|
-
const chunkSize = Math.min(
|
|
519
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
479
520
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
480
521
|
offset += chunkSize;
|
|
481
522
|
yield attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, chunk, `chunk-${idx + 1}`);
|
|
482
|
-
if (offset < toBuffer.length) {
|
|
483
|
-
yield hdShared.wait(
|
|
523
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
524
|
+
yield hdShared.wait(pacingDelayMs);
|
|
484
525
|
}
|
|
485
526
|
}
|
|
486
527
|
});
|
|
@@ -658,22 +699,12 @@ function getDevice(deviceId) {
|
|
|
658
699
|
const peripheral = discoveredDevices.get(deviceId);
|
|
659
700
|
if (peripheral) {
|
|
660
701
|
const deviceName = ((_a = peripheral.advertisement) === null || _a === void 0 ? void 0 : _a.localName) || 'Unknown Device';
|
|
661
|
-
return {
|
|
662
|
-
commType: 'electron-ble',
|
|
663
|
-
id: peripheral.id,
|
|
664
|
-
name: deviceName,
|
|
665
|
-
state: peripheral.state || 'disconnected',
|
|
666
|
-
};
|
|
702
|
+
return Object.assign({ commType: 'electron-ble', id: peripheral.id, name: deviceName, state: peripheral.state || 'disconnected' }, (typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}));
|
|
667
703
|
}
|
|
668
704
|
const connectedPeripheral = connectedDevices.get(deviceId);
|
|
669
705
|
if (connectedPeripheral) {
|
|
670
706
|
const deviceName = ((_b = connectedPeripheral.advertisement) === null || _b === void 0 ? void 0 : _b.localName) || 'Unknown Device';
|
|
671
|
-
return {
|
|
672
|
-
commType: 'electron-ble',
|
|
673
|
-
id: connectedPeripheral.id,
|
|
674
|
-
name: deviceName,
|
|
675
|
-
state: connectedPeripheral.state || 'connected',
|
|
676
|
-
};
|
|
707
|
+
return Object.assign({ commType: 'electron-ble', id: connectedPeripheral.id, name: deviceName, state: connectedPeripheral.state || 'connected' }, (typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}));
|
|
677
708
|
}
|
|
678
709
|
return {
|
|
679
710
|
commType: 'electron-ble',
|
|
@@ -1152,8 +1183,8 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1152
1183
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1153
1184
|
yield disconnectDevice(deviceId);
|
|
1154
1185
|
}));
|
|
1155
|
-
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1156
|
-
yield transmitHexDataToDevice(deviceId, hexData);
|
|
1186
|
+
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_WRITE, (_event, deviceId, hexData, options) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1187
|
+
yield transmitHexDataToDevice(deviceId, hexData, options);
|
|
1157
1188
|
}));
|
|
1158
1189
|
ipcMain.handle(hdShared.EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE, (_event, deviceId) => index.__awaiter(this, void 0, void 0, function* () {
|
|
1159
1190
|
yield subscribeNotifications(deviceId, (data) => {
|
|
@@ -1223,4 +1254,5 @@ function setupNobleBleHandlers(webContents) {
|
|
|
1223
1254
|
}
|
|
1224
1255
|
}
|
|
1225
1256
|
|
|
1257
|
+
exports.resolveNobleBleWritePacingDelay = resolveNobleBleWritePacingDelay;
|
|
1226
1258
|
exports.setupNobleBleHandlers = setupNobleBleHandlers;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { WebContents } from 'electron';
|
|
2
|
+
import type { NobleBleWriteOptions } from './types/desktop-api';
|
|
3
|
+
export declare function resolveNobleBleWritePacingDelay(options?: NobleBleWriteOptions): number;
|
|
2
4
|
export declare function setupNobleBleHandlers(webContents: WebContents): void;
|
|
3
5
|
//# sourceMappingURL=noble-ble-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAsB,WAAW,EAAE,MAAM,UAAU,CAAC;AAEhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAsEhE,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AAw7CD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CA8JpE"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export interface NobleBleWriteOptions {
|
|
2
|
+
pacingDelayMs?: number;
|
|
3
|
+
}
|
|
1
4
|
export interface NobleBleAPI {
|
|
2
5
|
enumerate: () => Promise<{
|
|
3
6
|
id: string;
|
|
@@ -6,13 +9,18 @@ export interface NobleBleAPI {
|
|
|
6
9
|
getDevice: (uuid: string) => Promise<{
|
|
7
10
|
id: string;
|
|
8
11
|
name: string;
|
|
12
|
+
mtu?: number;
|
|
9
13
|
} | null>;
|
|
10
14
|
connect: (uuid: string) => Promise<void>;
|
|
11
15
|
disconnect: (uuid: string) => Promise<void>;
|
|
12
16
|
subscribe: (uuid: string) => Promise<void>;
|
|
13
17
|
unsubscribe: (uuid: string) => Promise<void>;
|
|
14
|
-
write: (uuid: string, data: string) => Promise<void>;
|
|
18
|
+
write: (uuid: string, data: string, options?: NobleBleWriteOptions) => Promise<void>;
|
|
15
19
|
onNotification: (callback: (deviceId: string, data: string) => void) => () => void;
|
|
20
|
+
onMtuChanged?: (callback: (device: {
|
|
21
|
+
id: string;
|
|
22
|
+
mtu: number;
|
|
23
|
+
}) => void) => () => void;
|
|
16
24
|
onDeviceDisconnected: (callback: (device: {
|
|
17
25
|
id: string;
|
|
18
26
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACvF,oBAAoB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/F,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-electron",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.72",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"electron-log": ">=4.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-core": "1.2.0-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.72",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.72",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.72",
|
|
31
31
|
"@stoprocent/noble": "2.3.16",
|
|
32
32
|
"p-retry": "^4.6.2"
|
|
33
33
|
},
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"electron": "^25.0.0",
|
|
37
37
|
"typescript": "^5.3.3"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "76311ccd3753eca8e492b5324f93542bd38525ba"
|
|
40
40
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveBlePacketCapacity } from '../ble-packet-capacity';
|
|
2
|
+
|
|
3
|
+
describe('resolveBlePacketCapacity', () => {
|
|
4
|
+
test('uses ATT MTU payload capacity with an upper bound', () => {
|
|
5
|
+
expect(resolveBlePacketCapacity(247, 244, 192)).toBe(244);
|
|
6
|
+
expect(resolveBlePacketCapacity(185, 244, 192)).toBe(182);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('preserves the compatibility fallback when MTU is unavailable', () => {
|
|
10
|
+
expect(resolveBlePacketCapacity(null, 244, 192)).toBe(192);
|
|
11
|
+
expect(resolveBlePacketCapacity(undefined, 244, 192)).toBe(192);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -31,6 +31,14 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
31
31
|
expect(NOBLE_BLE_CONNECTION_TIMEOUT_MS).toBe(10_000);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
test('keeps safe pacing by default and allows an explicit high-throughput bypass', async () => {
|
|
35
|
+
const { resolveNobleBleWritePacingDelay } = await import('../noble-ble-handler');
|
|
36
|
+
|
|
37
|
+
expect(resolveNobleBleWritePacingDelay()).toBe(5);
|
|
38
|
+
expect(resolveNobleBleWritePacingDelay({ pacingDelayMs: 0 })).toBe(0);
|
|
39
|
+
expect(resolveNobleBleWritePacingDelay({ pacingDelayMs: 3.8 })).toBe(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
34
42
|
test('waits for Noble to stop scanning before enumeration resolves', async () => {
|
|
35
43
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
36
44
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
2
|
+
|
|
3
|
+
export function resolveBlePacketCapacity(
|
|
4
|
+
mtu: number | null | undefined,
|
|
5
|
+
maximumPacketCapacity: number,
|
|
6
|
+
fallbackPacketCapacity: number
|
|
7
|
+
): number {
|
|
8
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
9
|
+
return fallbackPacketCapacity;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { WebContents } from 'electron';
|
|
|
3
3
|
export * from './types';
|
|
4
4
|
|
|
5
5
|
// Export Desktop API types for other packages to reuse
|
|
6
|
-
export type { DesktopAPI, NobleBleAPI } from './types/desktop-api';
|
|
6
|
+
export type { DesktopAPI, NobleBleAPI, NobleBleWriteOptions } from './types/desktop-api';
|
|
7
7
|
|
|
8
8
|
export async function initNobleBleSupport(webContents: WebContents) {
|
|
9
9
|
const { setupNobleBleHandlers } = await import('./noble-ble-handler');
|
package/src/noble-ble-handler.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '@onekeyfe/hd-shared';
|
|
22
22
|
import pRetry from 'p-retry';
|
|
23
23
|
|
|
24
|
+
import { resolveBlePacketCapacity } from './ble-packet-capacity';
|
|
24
25
|
import { safeLog } from './types/noble-extended';
|
|
25
26
|
import { runBleCallbackOperation, softRefreshSubscription } from './ble-ops';
|
|
26
27
|
import {
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
|
|
31
32
|
import type { IpcMainInvokeEvent, WebContents } from 'electron';
|
|
32
33
|
import type { Characteristic, Peripheral, Service } from '@stoprocent/noble';
|
|
34
|
+
import type { NobleBleWriteOptions } from './types/desktop-api';
|
|
33
35
|
import type { CharacteristicPair, DeviceInfo, Logger, NobleModule } from './types/noble-extended';
|
|
34
36
|
|
|
35
37
|
// Noble will be dynamically imported to avoid bundling issues
|
|
@@ -65,6 +67,10 @@ const deviceDisconnectListeners = new Map<
|
|
|
65
67
|
string,
|
|
66
68
|
{ peripheral: Peripheral; listener: () => void }
|
|
67
69
|
>();
|
|
70
|
+
const deviceMtuListeners = new Map<
|
|
71
|
+
string,
|
|
72
|
+
{ peripheral: Peripheral; listener: (mtu: number) => void }
|
|
73
|
+
>();
|
|
68
74
|
|
|
69
75
|
// Windows-only response watchdog state moved to utils/windows-ble-recovery
|
|
70
76
|
|
|
@@ -86,14 +92,21 @@ const SERVICE_DISCOVERY_TIMEOUT = 10000; // 10 seconds for service discovery
|
|
|
86
92
|
const BLE_CLEANUP_TIMEOUT = 250;
|
|
87
93
|
|
|
88
94
|
// Write-related constants
|
|
89
|
-
const
|
|
90
|
-
const
|
|
95
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
96
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
97
|
+
const DEFAULT_WRITE_PACING_DELAY_MS = 5;
|
|
91
98
|
const RETRY_CONFIG = { MAX_ATTEMPTS: 15, WRITE_TIMEOUT: 2000 } as const;
|
|
92
99
|
const IS_WINDOWS = process.platform === 'win32';
|
|
93
100
|
const ABORTABLE_WRITE_ERROR_PATTERNS = [
|
|
94
101
|
/status:\s*3/i, // Windows pairing cancelled / GATT write failed
|
|
95
102
|
];
|
|
96
103
|
|
|
104
|
+
export function resolveNobleBleWritePacingDelay(options?: NobleBleWriteOptions) {
|
|
105
|
+
return typeof options?.pacingDelayMs === 'number' && Number.isFinite(options.pacingDelayMs)
|
|
106
|
+
? Math.min(Math.max(Math.floor(options.pacingDelayMs), 0), 1000)
|
|
107
|
+
: DEFAULT_WRITE_PACING_DELAY_MS;
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
function isOneKeyPeripheral(peripheral: Peripheral) {
|
|
98
111
|
const serviceUuids = peripheral.advertisement?.serviceUuids;
|
|
99
112
|
const localName = peripheral.advertisement?.localName;
|
|
@@ -364,6 +377,11 @@ function cleanupDevice(
|
|
|
364
377
|
disconnectEntry.peripheral.removeListener('disconnect', disconnectEntry.listener);
|
|
365
378
|
deviceDisconnectListeners.delete(deviceId);
|
|
366
379
|
}
|
|
380
|
+
const mtuEntry = deviceMtuListeners.get(deviceId);
|
|
381
|
+
if (mtuEntry) {
|
|
382
|
+
mtuEntry.peripheral.removeListener('mtu', mtuEntry.listener);
|
|
383
|
+
deviceMtuListeners.delete(deviceId);
|
|
384
|
+
}
|
|
367
385
|
connectedDevices.delete(deviceId);
|
|
368
386
|
deviceCharacteristics.delete(deviceId);
|
|
369
387
|
notificationCallbacks.delete(deviceId);
|
|
@@ -423,11 +441,33 @@ function setupDisconnectListener(
|
|
|
423
441
|
};
|
|
424
442
|
deviceDisconnectListeners.set(deviceId, { peripheral, listener });
|
|
425
443
|
peripheral.on('disconnect', listener);
|
|
444
|
+
setupMtuListener(peripheral, deviceId, webContents);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function setupMtuListener(
|
|
448
|
+
peripheral: Peripheral,
|
|
449
|
+
deviceId: string,
|
|
450
|
+
webContents: WebContents
|
|
451
|
+
): void {
|
|
452
|
+
const existing = deviceMtuListeners.get(deviceId);
|
|
453
|
+
if (existing) {
|
|
454
|
+
existing.peripheral.removeListener('mtu', existing.listener);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const listener = (mtu: number) => {
|
|
458
|
+
if (!Number.isFinite(mtu) || mtu <= 0) return;
|
|
459
|
+
webContents.send(EOneKeyBleMessageKeys.NOBLE_BLE_MTU_CHANGED, {
|
|
460
|
+
id: deviceId,
|
|
461
|
+
mtu,
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
deviceMtuListeners.set(deviceId, { peripheral, listener });
|
|
465
|
+
peripheral.on('mtu', listener);
|
|
426
466
|
}
|
|
427
467
|
|
|
428
468
|
// ===== Write helpers (inline) =====
|
|
429
469
|
|
|
430
|
-
async function
|
|
470
|
+
async function writeCharacteristicWithoutResponse(
|
|
431
471
|
deviceId: string,
|
|
432
472
|
writeCharacteristic: Characteristic,
|
|
433
473
|
buffer: Buffer
|
|
@@ -475,7 +515,7 @@ async function attemptWindowsWriteUntilPaired(
|
|
|
475
515
|
}
|
|
476
516
|
|
|
477
517
|
try {
|
|
478
|
-
await
|
|
518
|
+
await writeCharacteristicWithoutResponse(deviceId, latestWrite, payload);
|
|
479
519
|
} catch (e) {
|
|
480
520
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
481
521
|
logger?.error('[BLE-Write] Windows write error', {
|
|
@@ -547,7 +587,11 @@ async function attemptWindowsWriteUntilPaired(
|
|
|
547
587
|
);
|
|
548
588
|
}
|
|
549
589
|
|
|
550
|
-
async function transmitHexDataToDevice(
|
|
590
|
+
async function transmitHexDataToDevice(
|
|
591
|
+
deviceId: string,
|
|
592
|
+
hexData: string,
|
|
593
|
+
options?: NobleBleWriteOptions
|
|
594
|
+
): Promise<void> {
|
|
551
595
|
const characteristics = deviceCharacteristics.get(deviceId);
|
|
552
596
|
const peripheral = connectedDevices.get(deviceId);
|
|
553
597
|
if (!peripheral || !characteristics) {
|
|
@@ -559,6 +603,12 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
559
603
|
|
|
560
604
|
const toBuffer = Buffer.from(hexData, 'hex');
|
|
561
605
|
const doGetWriteCharacteristic = () => deviceCharacteristics.get(deviceId)?.write;
|
|
606
|
+
const packetCapacity = resolveBlePacketCapacity(
|
|
607
|
+
peripheral.mtu,
|
|
608
|
+
BLE_PACKET_SIZE_MAXIMUM,
|
|
609
|
+
BLE_PACKET_SIZE_FALLBACK
|
|
610
|
+
);
|
|
611
|
+
const pacingDelayMs = resolveNobleBleWritePacingDelay(options);
|
|
562
612
|
|
|
563
613
|
if (!IS_WINDOWS || pairedDevices.has(deviceId)) {
|
|
564
614
|
// macOS / Linux or already paired on Windows: direct write
|
|
@@ -569,14 +619,14 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
569
619
|
`Write characteristic not available for ${deviceId}`
|
|
570
620
|
);
|
|
571
621
|
}
|
|
572
|
-
if (toBuffer.length <=
|
|
573
|
-
await wait(
|
|
574
|
-
await
|
|
622
|
+
if (toBuffer.length <= packetCapacity) {
|
|
623
|
+
if (pacingDelayMs > 0) await wait(pacingDelayMs);
|
|
624
|
+
await writeCharacteristicWithoutResponse(deviceId, writeCharacteristic, toBuffer);
|
|
575
625
|
return;
|
|
576
626
|
}
|
|
577
627
|
// chunked
|
|
578
628
|
for (let offset = 0; offset < toBuffer.length; ) {
|
|
579
|
-
const chunkSize = Math.min(
|
|
629
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
580
630
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
581
631
|
offset += chunkSize;
|
|
582
632
|
const latest = doGetWriteCharacteristic();
|
|
@@ -586,23 +636,23 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
586
636
|
`Write characteristic not available for ${deviceId}`
|
|
587
637
|
);
|
|
588
638
|
}
|
|
589
|
-
await
|
|
590
|
-
if (offset < toBuffer.length) {
|
|
591
|
-
await wait(
|
|
639
|
+
await writeCharacteristicWithoutResponse(deviceId, latest, chunk);
|
|
640
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
641
|
+
await wait(pacingDelayMs);
|
|
592
642
|
}
|
|
593
643
|
}
|
|
594
644
|
return;
|
|
595
645
|
}
|
|
596
646
|
|
|
597
647
|
// Windows unpaired path: use loop
|
|
598
|
-
if (toBuffer.length <=
|
|
599
|
-
await wait(
|
|
648
|
+
if (toBuffer.length <= packetCapacity) {
|
|
649
|
+
if (pacingDelayMs > 0) await wait(pacingDelayMs);
|
|
600
650
|
await attemptWindowsWriteUntilPaired(deviceId, doGetWriteCharacteristic, toBuffer, 'single');
|
|
601
651
|
return;
|
|
602
652
|
}
|
|
603
653
|
// chunked loop
|
|
604
654
|
for (let offset = 0, idx = 0; offset < toBuffer.length; idx++) {
|
|
605
|
-
const chunkSize = Math.min(
|
|
655
|
+
const chunkSize = Math.min(packetCapacity, toBuffer.length - offset);
|
|
606
656
|
const chunk = toBuffer.subarray(offset, offset + chunkSize);
|
|
607
657
|
offset += chunkSize;
|
|
608
658
|
await attemptWindowsWriteUntilPaired(
|
|
@@ -611,8 +661,8 @@ async function transmitHexDataToDevice(deviceId: string, hexData: string): Promi
|
|
|
611
661
|
chunk,
|
|
612
662
|
`chunk-${idx + 1}`
|
|
613
663
|
);
|
|
614
|
-
if (offset < toBuffer.length) {
|
|
615
|
-
await wait(
|
|
664
|
+
if (offset < toBuffer.length && pacingDelayMs > 0) {
|
|
665
|
+
await wait(pacingDelayMs);
|
|
616
666
|
}
|
|
617
667
|
}
|
|
618
668
|
}
|
|
@@ -835,6 +885,7 @@ function getDevice(deviceId: string): DeviceInfo | null {
|
|
|
835
885
|
id: peripheral.id,
|
|
836
886
|
name: deviceName,
|
|
837
887
|
state: peripheral.state || 'disconnected',
|
|
888
|
+
...(typeof peripheral.mtu === 'number' ? { mtu: peripheral.mtu } : {}),
|
|
838
889
|
};
|
|
839
890
|
}
|
|
840
891
|
|
|
@@ -847,6 +898,7 @@ function getDevice(deviceId: string): DeviceInfo | null {
|
|
|
847
898
|
id: connectedPeripheral.id,
|
|
848
899
|
name: deviceName,
|
|
849
900
|
state: connectedPeripheral.state || 'connected',
|
|
901
|
+
...(typeof connectedPeripheral.mtu === 'number' ? { mtu: connectedPeripheral.mtu } : {}),
|
|
850
902
|
};
|
|
851
903
|
}
|
|
852
904
|
|
|
@@ -1580,8 +1632,13 @@ export function setupNobleBleHandlers(webContents: WebContents): void {
|
|
|
1580
1632
|
// Handle write request
|
|
1581
1633
|
ipcMain.handle(
|
|
1582
1634
|
EOneKeyBleMessageKeys.NOBLE_BLE_WRITE,
|
|
1583
|
-
async (
|
|
1584
|
-
|
|
1635
|
+
async (
|
|
1636
|
+
_event: IpcMainInvokeEvent,
|
|
1637
|
+
deviceId: string,
|
|
1638
|
+
hexData: string,
|
|
1639
|
+
options?: NobleBleWriteOptions
|
|
1640
|
+
) => {
|
|
1641
|
+
await transmitHexDataToDevice(deviceId, hexData, options);
|
|
1585
1642
|
}
|
|
1586
1643
|
);
|
|
1587
1644
|
|
package/src/types/desktop-api.ts
CHANGED
|
@@ -4,15 +4,20 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Noble BLE API interface - core BLE functionality
|
|
7
|
+
export interface NobleBleWriteOptions {
|
|
8
|
+
pacingDelayMs?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export interface NobleBleAPI {
|
|
8
12
|
enumerate: () => Promise<{ id: string; name: string }[]>;
|
|
9
|
-
getDevice: (uuid: string) => Promise<{ id: string; name: string } | null>;
|
|
13
|
+
getDevice: (uuid: string) => Promise<{ id: string; name: string; mtu?: number } | null>;
|
|
10
14
|
connect: (uuid: string) => Promise<void>;
|
|
11
15
|
disconnect: (uuid: string) => Promise<void>;
|
|
12
16
|
subscribe: (uuid: string) => Promise<void>;
|
|
13
17
|
unsubscribe: (uuid: string) => Promise<void>;
|
|
14
|
-
write: (uuid: string, data: string) => Promise<void>;
|
|
18
|
+
write: (uuid: string, data: string, options?: NobleBleWriteOptions) => Promise<void>;
|
|
15
19
|
onNotification: (callback: (deviceId: string, data: string) => void) => () => void;
|
|
20
|
+
onMtuChanged?: (callback: (device: { id: string; mtu: number }) => void) => () => void;
|
|
16
21
|
onDeviceDisconnected: (callback: (device: { id: string; name: string }) => void) => () => void;
|
|
17
22
|
checkAvailability: () => Promise<{
|
|
18
23
|
available: boolean;
|