@onekeyfe/hd-transport-web-device 1.2.2-alpha.12 → 1.2.2-alpha.120
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/README.md +1 -1
- package/__tests__/electron-ble-transport.test.ts +136 -14
- package/dist/electron-ble-transport.d.ts +1 -2
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.js +68 -72
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +75 -81
package/README.md
CHANGED
|
@@ -26,4 +26,4 @@ yar update:protobuf to generate new ./messages.json and ./src/types/messages.ts
|
|
|
26
26
|
|
|
27
27
|
## Docs
|
|
28
28
|
|
|
29
|
-
Documentation is available [
|
|
29
|
+
Documentation is available [Hardware SDK Getting Started](https://developer.onekey.so/en/hardware-sdk/getting-started)
|
|
@@ -182,6 +182,40 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
182
182
|
jest.clearAllMocks();
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
test('does not treat an error envelope from a legacy preload as a successful connect', async () => {
|
|
186
|
+
const device = { id: 'stale-bond-id', name: 'OneKey Pro 2' };
|
|
187
|
+
const nobleBle = createNobleBle(device);
|
|
188
|
+
nobleBle.connect.mockResolvedValue({
|
|
189
|
+
type: 'NobleBleIpcError',
|
|
190
|
+
success: false,
|
|
191
|
+
error: {
|
|
192
|
+
name: 'HardwareError',
|
|
193
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
194
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
195
|
+
},
|
|
196
|
+
} as never);
|
|
197
|
+
const bleTransport = configureTransport(nobleBle);
|
|
198
|
+
|
|
199
|
+
await expect(
|
|
200
|
+
bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
201
|
+
).rejects.toMatchObject({
|
|
202
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
203
|
+
});
|
|
204
|
+
expect(nobleBle.subscribe).not.toHaveBeenCalled();
|
|
205
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('sends a native disconnect even before acquire marks the device connected', async () => {
|
|
209
|
+
const device = { id: 'pending-connect-id', name: 'OneKey Pro 2' };
|
|
210
|
+
const nobleBle = createNobleBle(device);
|
|
211
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
212
|
+
|
|
213
|
+
await bleTransport.releaseNative(device.id);
|
|
214
|
+
|
|
215
|
+
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
216
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
217
|
+
});
|
|
218
|
+
|
|
185
219
|
test('keeps raw BLE lifecycle payloads off the public device event channel', async () => {
|
|
186
220
|
const device = { id: 'lifecycle-pro2-id', name: 'OneKey Pro 2' };
|
|
187
221
|
const nobleBle = createNobleBle(device);
|
|
@@ -464,7 +498,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
464
498
|
await expect(result).resolves.toBe('rejected');
|
|
465
499
|
});
|
|
466
500
|
|
|
467
|
-
test('
|
|
501
|
+
test('disconnects when both protocol probes fail and reconnects for the next acquire', async () => {
|
|
468
502
|
const device = { id: 'dead-device-id', name: 'Unknown Device' };
|
|
469
503
|
const nobleBle = createNobleBle(device);
|
|
470
504
|
|
|
@@ -476,7 +510,25 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
476
510
|
await expect(transport.acquire({ uuid: device.id })).rejects.toThrow(
|
|
477
511
|
/Unable to detect BLE protocol/
|
|
478
512
|
);
|
|
513
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
514
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
479
515
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
516
|
+
|
|
517
|
+
echoProtocolV2(nobleBle, device.id);
|
|
518
|
+
await expect(
|
|
519
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
520
|
+
).resolves.toMatchObject({ uuid: device.id });
|
|
521
|
+
expect(nobleBle.connect).toHaveBeenCalledTimes(2);
|
|
522
|
+
expect(nobleBle.disconnect.mock.invocationCallOrder.at(-1)).toBeLessThan(
|
|
523
|
+
nobleBle.connect.mock.invocationCallOrder[1]
|
|
524
|
+
);
|
|
525
|
+
await expect(
|
|
526
|
+
transport.call(device.id, 'Ping', { message: 'after-reconnect' })
|
|
527
|
+
).resolves.toMatchObject({
|
|
528
|
+
type: 'Success',
|
|
529
|
+
message: { message: 'ok' },
|
|
530
|
+
});
|
|
531
|
+
await transport.release(device.id);
|
|
480
532
|
});
|
|
481
533
|
|
|
482
534
|
test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
|
|
@@ -553,14 +605,19 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
553
605
|
test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
|
|
554
606
|
const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
|
|
555
607
|
const nobleBle = createNobleBle(device);
|
|
556
|
-
nobleBle.subscribe.mockRejectedValue(
|
|
608
|
+
nobleBle.subscribe.mockRejectedValue({
|
|
609
|
+
name: 'HardwareError',
|
|
610
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
611
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
612
|
+
params: { nativeErrorMessage: 'Encryption is insufficient' },
|
|
613
|
+
});
|
|
557
614
|
const transport = configureTransport(nobleBle);
|
|
558
615
|
const probe = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
559
616
|
|
|
560
617
|
await expect(
|
|
561
618
|
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
562
619
|
).rejects.toMatchObject({
|
|
563
|
-
errorCode: HardwareErrorCode.
|
|
620
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
564
621
|
});
|
|
565
622
|
|
|
566
623
|
expect(probe).not.toHaveBeenCalled();
|
|
@@ -568,21 +625,40 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
568
625
|
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
569
626
|
});
|
|
570
627
|
|
|
571
|
-
test('
|
|
572
|
-
const device = { id: 'reset-
|
|
628
|
+
test('rehydrates a structured stale-bond error before the protocol is known', async () => {
|
|
629
|
+
const device = { id: 'reset-unknown-protocol-id', name: 'OneKey Pro 2' };
|
|
573
630
|
const nobleBle = createNobleBle(device);
|
|
574
|
-
nobleBle.connect.mockRejectedValue(
|
|
575
|
-
|
|
576
|
-
|
|
631
|
+
nobleBle.connect.mockRejectedValue({
|
|
632
|
+
name: 'HardwareError',
|
|
633
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
634
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
635
|
+
params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
|
|
636
|
+
});
|
|
577
637
|
const transport = configureTransport(nobleBle);
|
|
578
638
|
|
|
579
|
-
await expect(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
639
|
+
await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
|
|
640
|
+
name: 'HardwareError',
|
|
641
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
642
|
+
params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
|
|
583
643
|
});
|
|
584
644
|
});
|
|
585
645
|
|
|
646
|
+
test('does not infer hardware errors from native error text in the renderer', async () => {
|
|
647
|
+
const device = { id: 'reset-pro2-localized-id', name: 'OneKey Pro 2' };
|
|
648
|
+
const nobleBle = createNobleBle(device);
|
|
649
|
+
nobleBle.connect.mockRejectedValue(new Error('CBATTErrorDomain:14 localized native message'));
|
|
650
|
+
const transport = configureTransport(nobleBle);
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
654
|
+
throw new Error('Expected acquire to fail');
|
|
655
|
+
} catch (error) {
|
|
656
|
+
expect(error).toBeInstanceOf(Error);
|
|
657
|
+
expect((error as Error).message).toBe('CBATTErrorDomain:14 localized native message');
|
|
658
|
+
expect((error as { errorCode?: unknown }).errorCode).toBeUndefined();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
586
662
|
test('does not classify a generic macOS connection failure as a stale bond', async () => {
|
|
587
663
|
const device = { id: 'offline-pro2-macos-id', name: 'OneKey Pro 2' };
|
|
588
664
|
const nobleBle = createNobleBle(device);
|
|
@@ -615,7 +691,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
615
691
|
}
|
|
616
692
|
});
|
|
617
693
|
|
|
618
|
-
test('
|
|
694
|
+
test('keeps a probe miss retryable after a previously confirmed Protocol V2 connection', async () => {
|
|
619
695
|
const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
|
|
620
696
|
const nobleBle = createNobleBle(device);
|
|
621
697
|
const transport = configureTransport(nobleBle);
|
|
@@ -628,7 +704,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
628
704
|
await expect(
|
|
629
705
|
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
630
706
|
).rejects.toMatchObject({
|
|
631
|
-
errorCode: HardwareErrorCode.
|
|
707
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
632
708
|
});
|
|
633
709
|
|
|
634
710
|
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
@@ -636,6 +712,52 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
636
712
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
637
713
|
});
|
|
638
714
|
|
|
715
|
+
test.each([false, true])(
|
|
716
|
+
'preserves an expected Protocol V2 Ping timeout with a prior successful connection: %s',
|
|
717
|
+
async previouslyConnected => {
|
|
718
|
+
const device = { id: 'timeout-pro2-id', name: 'OneKey Pro 2' };
|
|
719
|
+
const nobleBle = createNobleBle(device);
|
|
720
|
+
const transport = configureTransport(nobleBle);
|
|
721
|
+
|
|
722
|
+
if (previouslyConnected) {
|
|
723
|
+
echoProtocolV2(nobleBle, device.id);
|
|
724
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
725
|
+
await transport.release(device.id);
|
|
726
|
+
}
|
|
727
|
+
// Keep the real probe and response timer, but drop its notification.
|
|
728
|
+
nobleBle.write.mockImplementation(() => Promise.resolve());
|
|
729
|
+
|
|
730
|
+
await expect(
|
|
731
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
732
|
+
).rejects.toMatchObject({
|
|
733
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
734
|
+
message: 'BLE response timeout after 5000ms for Ping',
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
738
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
739
|
+
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
test('preserves a native stale-bond error during an expected Protocol V2 probe', async () => {
|
|
744
|
+
const device = { id: 'stale-bond-probe-id', name: 'OneKey Pro 2' };
|
|
745
|
+
const nobleBle = createNobleBle(device);
|
|
746
|
+
nobleBle.write.mockRejectedValue({
|
|
747
|
+
name: 'HardwareError',
|
|
748
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
749
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
750
|
+
});
|
|
751
|
+
const transport = configureTransport(nobleBle);
|
|
752
|
+
|
|
753
|
+
await expect(
|
|
754
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
755
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
|
|
756
|
+
|
|
757
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
758
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
759
|
+
});
|
|
760
|
+
|
|
639
761
|
test('does not take a Protocol V2 hint from the BLE name', async () => {
|
|
640
762
|
const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
|
|
641
763
|
const nobleBle = createNobleBle(device);
|
|
@@ -27,7 +27,6 @@ export default class ElectronBleTransport {
|
|
|
27
27
|
private connectedDevices;
|
|
28
28
|
private deviceProtocol;
|
|
29
29
|
private deviceProtocolHints;
|
|
30
|
-
private confirmedProtocolV2;
|
|
31
30
|
private deviceMtus;
|
|
32
31
|
private devicePacketCapacities;
|
|
33
32
|
private v1Buffers;
|
|
@@ -41,7 +40,7 @@ export default class ElectronBleTransport {
|
|
|
41
40
|
private hostDisconnectCleanup?;
|
|
42
41
|
private notificationTokens;
|
|
43
42
|
private nextNotificationToken;
|
|
44
|
-
private
|
|
43
|
+
private normalizeBluetoothError;
|
|
45
44
|
private handleBluetoothError;
|
|
46
45
|
private cleanupDeviceState;
|
|
47
46
|
init(logger: any, emitter?: EventEmitter): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -218,8 +218,6 @@ declare class ElectronBleTransport {
|
|
|
218
218
|
private connectedDevices;
|
|
219
219
|
private deviceProtocol;
|
|
220
220
|
private deviceProtocolHints;
|
|
221
|
-
/** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
|
|
222
|
-
private confirmedProtocolV2;
|
|
223
221
|
private deviceMtus;
|
|
224
222
|
private devicePacketCapacities;
|
|
225
223
|
private v1Buffers;
|
|
@@ -242,7 +240,7 @@ declare class ElectronBleTransport {
|
|
|
242
240
|
private hostDisconnectCleanup?;
|
|
243
241
|
private notificationTokens;
|
|
244
242
|
private nextNotificationToken;
|
|
245
|
-
private
|
|
243
|
+
private normalizeBluetoothError;
|
|
246
244
|
private handleBluetoothError;
|
|
247
245
|
private cleanupDeviceState;
|
|
248
246
|
init(logger: any, emitter?: EventEmitter): void;
|
package/dist/index.js
CHANGED
|
@@ -823,6 +823,19 @@ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapa
|
|
|
823
823
|
|
|
824
824
|
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
825
825
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
826
|
+
const invokeNobleBle = (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
827
|
+
const response = yield request;
|
|
828
|
+
if (response &&
|
|
829
|
+
typeof response === 'object' &&
|
|
830
|
+
'type' in response &&
|
|
831
|
+
response.type === 'NobleBleIpcError' &&
|
|
832
|
+
'success' in response &&
|
|
833
|
+
response.success === false &&
|
|
834
|
+
'error' in response) {
|
|
835
|
+
return Promise.reject(response.error);
|
|
836
|
+
}
|
|
837
|
+
return response;
|
|
838
|
+
});
|
|
826
839
|
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
827
840
|
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
828
841
|
const BLE_WRITE_DELAY_MS = 5;
|
|
@@ -837,7 +850,6 @@ class ElectronBleTransport {
|
|
|
837
850
|
this.connectedDevices = new Set();
|
|
838
851
|
this.deviceProtocol = new Map();
|
|
839
852
|
this.deviceProtocolHints = new Map();
|
|
840
|
-
this.confirmedProtocolV2 = new Set();
|
|
841
853
|
this.deviceMtus = new Map();
|
|
842
854
|
this.devicePacketCapacities = new Map();
|
|
843
855
|
this.v1Buffers = new Map();
|
|
@@ -871,40 +883,20 @@ class ElectronBleTransport {
|
|
|
871
883
|
this.notificationTokens = new Map();
|
|
872
884
|
this.nextNotificationToken = 1;
|
|
873
885
|
}
|
|
874
|
-
|
|
875
|
-
var _a;
|
|
876
|
-
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
877
|
-
return error;
|
|
878
|
-
}
|
|
879
|
-
const errorMessage = error && typeof error === 'object' && 'message' in error
|
|
880
|
-
? String((_a = error.message) !== null && _a !== void 0 ? _a : '')
|
|
881
|
-
: String(error !== null && error !== void 0 ? error : '');
|
|
882
|
-
if (!hdShared.isBleStaleBondErrorText(errorMessage)) {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
const normalizedErrorMessage = errorMessage.toLowerCase();
|
|
886
|
-
return hdShared.ERRORS.TypedError(normalizedErrorMessage.includes('peer removed pairing information') ||
|
|
887
|
-
normalizedErrorMessage.includes('cberrordomain:14')
|
|
888
|
-
? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
889
|
-
: hdShared.HardwareErrorCode.BleDeviceBondError, errorMessage);
|
|
890
|
-
}
|
|
891
|
-
handleBluetoothError(error, mapProtocolV2StaleBond = false) {
|
|
892
|
-
if (mapProtocolV2StaleBond) {
|
|
893
|
-
const staleBondError = this.toStaleBondError(error);
|
|
894
|
-
if (staleBondError) {
|
|
895
|
-
throw staleBondError;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
886
|
+
normalizeBluetoothError(error) {
|
|
898
887
|
if (error && typeof error === 'object') {
|
|
888
|
+
if (typeof error.errorCode === 'number') {
|
|
889
|
+
return hdShared.ERRORS.TypedError(error.errorCode, typeof error.message === 'string' ? error.message : undefined, error.params);
|
|
890
|
+
}
|
|
899
891
|
if ('code' in error) {
|
|
900
892
|
if (error.code === hdShared.HardwareErrorCode.BlePoweredOff) {
|
|
901
|
-
|
|
893
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
|
|
902
894
|
}
|
|
903
895
|
if (error.code === hdShared.HardwareErrorCode.BleUnsupported) {
|
|
904
|
-
|
|
896
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
|
|
905
897
|
}
|
|
906
898
|
if (error.code === hdShared.HardwareErrorCode.BlePermissionError) {
|
|
907
|
-
|
|
899
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
|
|
908
900
|
}
|
|
909
901
|
}
|
|
910
902
|
const errorMessage = error.message || String(error);
|
|
@@ -912,16 +904,19 @@ class ElectronBleTransport {
|
|
|
912
904
|
const unsupportedMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleUnsupported];
|
|
913
905
|
const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BlePermissionError];
|
|
914
906
|
if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
|
|
915
|
-
|
|
907
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
|
|
916
908
|
}
|
|
917
909
|
if (errorMessage.includes(unsupportedMessage) || errorMessage.includes('unsupported')) {
|
|
918
|
-
|
|
910
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
|
|
919
911
|
}
|
|
920
912
|
if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
|
|
921
|
-
|
|
913
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
|
|
922
914
|
}
|
|
923
915
|
}
|
|
924
|
-
|
|
916
|
+
return error;
|
|
917
|
+
}
|
|
918
|
+
handleBluetoothError(error) {
|
|
919
|
+
throw this.normalizeBluetoothError(error);
|
|
925
920
|
}
|
|
926
921
|
cleanupDeviceState(deviceId) {
|
|
927
922
|
this.protocolV2Links
|
|
@@ -1008,7 +1003,7 @@ class ElectronBleTransport {
|
|
|
1008
1003
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
1009
1004
|
throw new Error('Noble BLE API not available');
|
|
1010
1005
|
}
|
|
1011
|
-
const devices = yield window.desktopApi.nobleBle.enumerate();
|
|
1006
|
+
const devices = yield invokeNobleBle(window.desktopApi.nobleBle.enumerate());
|
|
1012
1007
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
|
|
1013
1008
|
for (const dev of devices) {
|
|
1014
1009
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
|
|
@@ -1025,9 +1020,6 @@ class ElectronBleTransport {
|
|
|
1025
1020
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1026
1021
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1027
1022
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
1028
|
-
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
1029
|
-
? expectedProtocol === 'V2'
|
|
1030
|
-
: this.confirmedProtocolV2.has(uuid);
|
|
1031
1023
|
if (!uuid) {
|
|
1032
1024
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
1033
1025
|
}
|
|
@@ -1044,7 +1036,7 @@ class ElectronBleTransport {
|
|
|
1044
1036
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
1045
1037
|
throw new Error('Noble BLE API not available');
|
|
1046
1038
|
}
|
|
1047
|
-
const device = yield window.desktopApi.nobleBle.getDevice(uuid);
|
|
1039
|
+
const device = yield invokeNobleBle(window.desktopApi.nobleBle.getDevice(uuid));
|
|
1048
1040
|
if (!device) {
|
|
1049
1041
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
|
|
1050
1042
|
}
|
|
@@ -1055,11 +1047,11 @@ class ElectronBleTransport {
|
|
|
1055
1047
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
1056
1048
|
}
|
|
1057
1049
|
try {
|
|
1058
|
-
yield window.desktopApi.nobleBle.connect(uuid);
|
|
1050
|
+
yield invokeNobleBle(window.desktopApi.nobleBle.connect(uuid));
|
|
1059
1051
|
this.connectedDevices.add(uuid);
|
|
1060
1052
|
}
|
|
1061
1053
|
catch (error) {
|
|
1062
|
-
this.handleBluetoothError(error
|
|
1054
|
+
this.handleBluetoothError(error);
|
|
1063
1055
|
}
|
|
1064
1056
|
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
1065
1057
|
if (mtuCleanup) {
|
|
@@ -1068,10 +1060,10 @@ class ElectronBleTransport {
|
|
|
1068
1060
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1069
1061
|
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
1070
1062
|
try {
|
|
1071
|
-
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
1063
|
+
yield invokeNobleBle(window.desktopApi.nobleBle.subscribe(uuid));
|
|
1072
1064
|
}
|
|
1073
1065
|
catch (error) {
|
|
1074
|
-
this.handleBluetoothError(error
|
|
1066
|
+
this.handleBluetoothError(error);
|
|
1075
1067
|
}
|
|
1076
1068
|
yield this.refreshBlePacketCapacity(uuid);
|
|
1077
1069
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1089,16 +1081,22 @@ class ElectronBleTransport {
|
|
|
1089
1081
|
catch (error) {
|
|
1090
1082
|
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.error('[Electron BLE] acquire failed:', error);
|
|
1091
1083
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1084
|
+
const nobleBle = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle;
|
|
1085
|
+
if (nobleBle) {
|
|
1086
|
+
if (this.connectedDevices.has(uuid)) {
|
|
1087
|
+
yield invokeNobleBle(nobleBle.unsubscribe(uuid)).catch(cleanupError => {
|
|
1088
|
+
var _a;
|
|
1089
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] acquire unsubscribe failed:', cleanupError);
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
yield invokeNobleBle(nobleBle.disconnect(uuid));
|
|
1095
1093
|
}
|
|
1096
1094
|
}
|
|
1097
1095
|
catch (cleanupError) {
|
|
1098
1096
|
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
1099
1097
|
}
|
|
1100
1098
|
this.cleanupDeviceState(uuid);
|
|
1101
|
-
|
|
1099
|
+
this.handleBluetoothError(error);
|
|
1102
1100
|
}
|
|
1103
1101
|
});
|
|
1104
1102
|
}
|
|
@@ -1124,13 +1122,17 @@ class ElectronBleTransport {
|
|
|
1124
1122
|
var _a, _b;
|
|
1125
1123
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1126
1124
|
try {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
yield
|
|
1125
|
+
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1126
|
+
if (nobleBle) {
|
|
1127
|
+
if (this.connectedDevices.has(id)) {
|
|
1128
|
+
yield invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
|
|
1129
|
+
var _a;
|
|
1130
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] release unsubscribe failed:', error);
|
|
1131
|
+
});
|
|
1131
1132
|
}
|
|
1132
|
-
|
|
1133
|
+
yield invokeNobleBle(nobleBle.disconnect(id));
|
|
1133
1134
|
}
|
|
1135
|
+
this.cleanupDeviceState(id);
|
|
1134
1136
|
}
|
|
1135
1137
|
catch (error) {
|
|
1136
1138
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] release failed:', error);
|
|
@@ -1154,7 +1156,7 @@ class ElectronBleTransport {
|
|
|
1154
1156
|
}
|
|
1155
1157
|
return;
|
|
1156
1158
|
}
|
|
1157
|
-
yield release(id, keepSession);
|
|
1159
|
+
yield invokeNobleBle(release(id, keepSession));
|
|
1158
1160
|
}
|
|
1159
1161
|
catch (error) {
|
|
1160
1162
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
|
|
@@ -1162,9 +1164,8 @@ class ElectronBleTransport {
|
|
|
1162
1164
|
}
|
|
1163
1165
|
});
|
|
1164
1166
|
}
|
|
1165
|
-
createProtocolMismatchError(expected
|
|
1166
|
-
|
|
1167
|
-
return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1167
|
+
createProtocolMismatchError(expected) {
|
|
1168
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1168
1169
|
}
|
|
1169
1170
|
createProtocolDetectionError() {
|
|
1170
1171
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
@@ -1183,13 +1184,12 @@ class ElectronBleTransport {
|
|
|
1183
1184
|
return 'V1';
|
|
1184
1185
|
}
|
|
1185
1186
|
if (expectedProtocol === 'V2') {
|
|
1186
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
1187
|
+
if (yield this.probeProtocolV2(uuid, expectedProtocol)) {
|
|
1187
1188
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1188
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1189
1189
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1190
1190
|
return 'V2';
|
|
1191
1191
|
}
|
|
1192
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
1192
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1193
1193
|
}
|
|
1194
1194
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1195
1195
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1200,9 +1200,6 @@ class ElectronBleTransport {
|
|
|
1200
1200
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1201
1201
|
if (detected) {
|
|
1202
1202
|
this.deviceProtocol.set(uuid, protocol);
|
|
1203
|
-
if (protocol === 'V2') {
|
|
1204
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1205
|
-
}
|
|
1206
1203
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1207
1204
|
return protocol;
|
|
1208
1205
|
}
|
|
@@ -1265,7 +1262,7 @@ class ElectronBleTransport {
|
|
|
1265
1262
|
}
|
|
1266
1263
|
});
|
|
1267
1264
|
}
|
|
1268
|
-
probeProtocolV2(uuid) {
|
|
1265
|
+
probeProtocolV2(uuid, expectedProtocol) {
|
|
1269
1266
|
var _a;
|
|
1270
1267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1271
1268
|
if (!this._messages || !this._messagesV2) {
|
|
@@ -1283,7 +1280,9 @@ class ElectronBleTransport {
|
|
|
1283
1280
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1284
1281
|
this.resetProtocolV2Frames(uuid);
|
|
1285
1282
|
},
|
|
1286
|
-
shouldRethrow: error =>
|
|
1283
|
+
shouldRethrow: error => expectedProtocol === 'V2' ||
|
|
1284
|
+
hdShared.isBleStaleBondHardwareError(error) ||
|
|
1285
|
+
transport.isProtocolV2LinkDisabledError(error),
|
|
1287
1286
|
});
|
|
1288
1287
|
if (!detected) {
|
|
1289
1288
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1299,21 +1298,18 @@ class ElectronBleTransport {
|
|
|
1299
1298
|
throw new Error('Noble BLE API not available');
|
|
1300
1299
|
}
|
|
1301
1300
|
try {
|
|
1302
|
-
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1301
|
+
yield invokeNobleBle(nobleBle.write(uuid, hexData, { pacingDelayMs: 0 }));
|
|
1303
1302
|
}
|
|
1304
1303
|
catch (error) {
|
|
1305
|
-
|
|
1306
|
-
if (staleBondError) {
|
|
1307
|
-
throw staleBondError;
|
|
1308
|
-
}
|
|
1309
|
-
throw error;
|
|
1304
|
+
this.handleBluetoothError(error);
|
|
1310
1305
|
}
|
|
1311
1306
|
});
|
|
1312
1307
|
}
|
|
1313
1308
|
refreshBlePacketCapacity(uuid) {
|
|
1314
|
-
var _a
|
|
1309
|
+
var _a;
|
|
1315
1310
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1316
|
-
const
|
|
1311
|
+
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1312
|
+
const device = nobleBle ? yield invokeNobleBle(nobleBle.getDevice(uuid)) : undefined;
|
|
1317
1313
|
this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
|
|
1318
1314
|
});
|
|
1319
1315
|
}
|
|
@@ -1536,7 +1532,7 @@ class ElectronBleTransport {
|
|
|
1536
1532
|
if (hexString.length === 0) {
|
|
1537
1533
|
throw new Error(`Buffer ${i + 1} is empty`);
|
|
1538
1534
|
}
|
|
1539
|
-
yield window.desktopApi.nobleBle.write(uuid, hexString);
|
|
1535
|
+
yield invokeNobleBle(window.desktopApi.nobleBle.write(uuid, hexString));
|
|
1540
1536
|
}
|
|
1541
1537
|
const response = yield Promise.race([
|
|
1542
1538
|
runPromise.promise,
|
|
@@ -1569,7 +1565,7 @@ class ElectronBleTransport {
|
|
|
1569
1565
|
yield this.releaseNative(uuid);
|
|
1570
1566
|
}
|
|
1571
1567
|
}
|
|
1572
|
-
throw e;
|
|
1568
|
+
throw this.normalizeBluetoothError(e);
|
|
1573
1569
|
}
|
|
1574
1570
|
finally {
|
|
1575
1571
|
if (timeout)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.120",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"lint:fix": "eslint . --fix"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.120",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.120"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.2-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.2-alpha.120",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "26958fa2be6aa34a07f42403fb7022d2785e70b9"
|
|
33
33
|
}
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
HardwareErrorCode,
|
|
19
19
|
HardwareErrorCodeMessage,
|
|
20
20
|
createDeferred,
|
|
21
|
-
isBleStaleBondErrorText,
|
|
22
21
|
isBleStaleBondHardwareError,
|
|
23
22
|
isHeaderChunk,
|
|
24
23
|
} from '@onekeyfe/hd-shared';
|
|
@@ -26,7 +25,7 @@ import {
|
|
|
26
25
|
import { resolveBlePacketCapacity } from './ble-packet-capacity';
|
|
27
26
|
|
|
28
27
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
29
|
-
import type { DesktopAPI } from '@onekeyfe/hd-transport-electron';
|
|
28
|
+
import type { DesktopAPI, NobleBleIpcErrorResponse } from '@onekeyfe/hd-transport-electron';
|
|
30
29
|
import type {
|
|
31
30
|
OneKeyDeviceInfo,
|
|
32
31
|
ProtocolType,
|
|
@@ -69,6 +68,22 @@ const toBleDescriptor = (
|
|
|
69
68
|
...(protocolType ? { protocolType } : {}),
|
|
70
69
|
} as OneKeyDeviceInfo);
|
|
71
70
|
|
|
71
|
+
const invokeNobleBle = async <T>(request: Promise<T>): Promise<T> => {
|
|
72
|
+
const response = await (request as Promise<T | NobleBleIpcErrorResponse>);
|
|
73
|
+
if (
|
|
74
|
+
response &&
|
|
75
|
+
typeof response === 'object' &&
|
|
76
|
+
'type' in response &&
|
|
77
|
+
response.type === 'NobleBleIpcError' &&
|
|
78
|
+
'success' in response &&
|
|
79
|
+
response.success === false &&
|
|
80
|
+
'error' in response
|
|
81
|
+
) {
|
|
82
|
+
return Promise.reject(response.error);
|
|
83
|
+
}
|
|
84
|
+
return response as T;
|
|
85
|
+
};
|
|
86
|
+
|
|
72
87
|
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
73
88
|
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
74
89
|
const BLE_WRITE_DELAY_MS = 5;
|
|
@@ -106,9 +121,6 @@ export default class ElectronBleTransport {
|
|
|
106
121
|
|
|
107
122
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
108
123
|
|
|
109
|
-
/** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
|
|
110
|
-
private confirmedProtocolV2 = new Set<string>();
|
|
111
|
-
|
|
112
124
|
private deviceMtus: Map<string, number> = new Map();
|
|
113
125
|
|
|
114
126
|
private devicePacketCapacities: Map<string, number> = new Map();
|
|
@@ -163,44 +175,24 @@ export default class ElectronBleTransport {
|
|
|
163
175
|
|
|
164
176
|
private nextNotificationToken = 1;
|
|
165
177
|
|
|
166
|
-
private
|
|
167
|
-
if (isBleStaleBondHardwareError(error)) {
|
|
168
|
-
return error as Error;
|
|
169
|
-
}
|
|
170
|
-
const errorMessage =
|
|
171
|
-
error && typeof error === 'object' && 'message' in error
|
|
172
|
-
? String((error as { message?: unknown }).message ?? '')
|
|
173
|
-
: String(error ?? '');
|
|
174
|
-
if (!isBleStaleBondErrorText(errorMessage)) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
const normalizedErrorMessage = errorMessage.toLowerCase();
|
|
178
|
-
return ERRORS.TypedError(
|
|
179
|
-
normalizedErrorMessage.includes('peer removed pairing information') ||
|
|
180
|
-
normalizedErrorMessage.includes('cberrordomain:14')
|
|
181
|
-
? HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
182
|
-
: HardwareErrorCode.BleDeviceBondError,
|
|
183
|
-
errorMessage
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private handleBluetoothError(error: any, mapProtocolV2StaleBond = false): never {
|
|
188
|
-
if (mapProtocolV2StaleBond) {
|
|
189
|
-
const staleBondError = this.toStaleBondError(error);
|
|
190
|
-
if (staleBondError) {
|
|
191
|
-
throw staleBondError;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
178
|
+
private normalizeBluetoothError(error: any): any {
|
|
194
179
|
if (error && typeof error === 'object') {
|
|
180
|
+
if (typeof error.errorCode === 'number') {
|
|
181
|
+
return ERRORS.TypedError(
|
|
182
|
+
error.errorCode,
|
|
183
|
+
typeof error.message === 'string' ? error.message : undefined,
|
|
184
|
+
error.params
|
|
185
|
+
);
|
|
186
|
+
}
|
|
195
187
|
if ('code' in error) {
|
|
196
188
|
if (error.code === HardwareErrorCode.BlePoweredOff) {
|
|
197
|
-
|
|
189
|
+
return ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
|
|
198
190
|
}
|
|
199
191
|
if (error.code === HardwareErrorCode.BleUnsupported) {
|
|
200
|
-
|
|
192
|
+
return ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
|
|
201
193
|
}
|
|
202
194
|
if (error.code === HardwareErrorCode.BlePermissionError) {
|
|
203
|
-
|
|
195
|
+
return ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
|
|
204
196
|
}
|
|
205
197
|
}
|
|
206
198
|
const errorMessage = error.message || String(error);
|
|
@@ -209,16 +201,20 @@ export default class ElectronBleTransport {
|
|
|
209
201
|
const permissionMessage = HardwareErrorCodeMessage[HardwareErrorCode.BlePermissionError];
|
|
210
202
|
|
|
211
203
|
if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
|
|
212
|
-
|
|
204
|
+
return ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
|
|
213
205
|
}
|
|
214
206
|
if (errorMessage.includes(unsupportedMessage) || errorMessage.includes('unsupported')) {
|
|
215
|
-
|
|
207
|
+
return ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
|
|
216
208
|
}
|
|
217
209
|
if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
|
|
218
|
-
|
|
210
|
+
return ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
|
|
219
211
|
}
|
|
220
212
|
}
|
|
221
|
-
|
|
213
|
+
return error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private handleBluetoothError(error: any): never {
|
|
217
|
+
throw this.normalizeBluetoothError(error);
|
|
222
218
|
}
|
|
223
219
|
|
|
224
220
|
private cleanupDeviceState(deviceId: string): void {
|
|
@@ -337,7 +333,7 @@ export default class ElectronBleTransport {
|
|
|
337
333
|
if (!window.desktopApi?.nobleBle) {
|
|
338
334
|
throw new Error('Noble BLE API not available');
|
|
339
335
|
}
|
|
340
|
-
const devices = await window.desktopApi.nobleBle.enumerate();
|
|
336
|
+
const devices = await invokeNobleBle(window.desktopApi.nobleBle.enumerate());
|
|
341
337
|
this.Log?.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
|
|
342
338
|
for (const dev of devices) {
|
|
343
339
|
this.Log?.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
|
|
@@ -351,9 +347,6 @@ export default class ElectronBleTransport {
|
|
|
351
347
|
|
|
352
348
|
async acquire(input: BleAcquireInput) {
|
|
353
349
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
354
|
-
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
355
|
-
? expectedProtocol === 'V2'
|
|
356
|
-
: this.confirmedProtocolV2.has(uuid);
|
|
357
350
|
|
|
358
351
|
if (!uuid) {
|
|
359
352
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
@@ -375,7 +368,7 @@ export default class ElectronBleTransport {
|
|
|
375
368
|
throw new Error('Noble BLE API not available');
|
|
376
369
|
}
|
|
377
370
|
|
|
378
|
-
const device = await window.desktopApi.nobleBle.getDevice(uuid);
|
|
371
|
+
const device = await invokeNobleBle(window.desktopApi.nobleBle.getDevice(uuid));
|
|
379
372
|
if (!device) {
|
|
380
373
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
|
|
381
374
|
}
|
|
@@ -387,10 +380,10 @@ export default class ElectronBleTransport {
|
|
|
387
380
|
}
|
|
388
381
|
|
|
389
382
|
try {
|
|
390
|
-
await window.desktopApi.nobleBle.connect(uuid);
|
|
383
|
+
await invokeNobleBle(window.desktopApi.nobleBle.connect(uuid));
|
|
391
384
|
this.connectedDevices.add(uuid);
|
|
392
385
|
} catch (error) {
|
|
393
|
-
this.handleBluetoothError(error
|
|
386
|
+
this.handleBluetoothError(error);
|
|
394
387
|
}
|
|
395
388
|
|
|
396
389
|
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
@@ -402,9 +395,9 @@ export default class ElectronBleTransport {
|
|
|
402
395
|
this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
403
396
|
|
|
404
397
|
try {
|
|
405
|
-
await window.desktopApi.nobleBle.subscribe(uuid);
|
|
398
|
+
await invokeNobleBle(window.desktopApi.nobleBle.subscribe(uuid));
|
|
406
399
|
} catch (error) {
|
|
407
|
-
this.handleBluetoothError(error
|
|
400
|
+
this.handleBluetoothError(error);
|
|
408
401
|
}
|
|
409
402
|
await this.refreshBlePacketCapacity(uuid);
|
|
410
403
|
|
|
@@ -427,15 +420,20 @@ export default class ElectronBleTransport {
|
|
|
427
420
|
} catch (error) {
|
|
428
421
|
this.Log?.error('[Electron BLE] acquire failed:', error);
|
|
429
422
|
try {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
423
|
+
const nobleBle = window.desktopApi?.nobleBle;
|
|
424
|
+
if (nobleBle) {
|
|
425
|
+
if (this.connectedDevices.has(uuid)) {
|
|
426
|
+
await invokeNobleBle(nobleBle.unsubscribe(uuid)).catch(cleanupError => {
|
|
427
|
+
this.Log?.debug('[Electron BLE] acquire unsubscribe failed:', cleanupError);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
await invokeNobleBle(nobleBle.disconnect(uuid));
|
|
433
431
|
}
|
|
434
432
|
} catch (cleanupError) {
|
|
435
433
|
this.Log?.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
436
434
|
}
|
|
437
435
|
this.cleanupDeviceState(uuid);
|
|
438
|
-
|
|
436
|
+
this.handleBluetoothError(error);
|
|
439
437
|
}
|
|
440
438
|
}
|
|
441
439
|
|
|
@@ -458,13 +456,16 @@ export default class ElectronBleTransport {
|
|
|
458
456
|
// Hard teardown, error paths only: a link presumed dead must not be reused.
|
|
459
457
|
private async releaseNative(id: string) {
|
|
460
458
|
try {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
await
|
|
459
|
+
const nobleBle = window.desktopApi?.nobleBle;
|
|
460
|
+
if (nobleBle) {
|
|
461
|
+
if (this.connectedDevices.has(id)) {
|
|
462
|
+
await invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
|
|
463
|
+
this.Log?.debug('[Electron BLE] release unsubscribe failed:', error);
|
|
464
|
+
});
|
|
465
465
|
}
|
|
466
|
-
|
|
466
|
+
await invokeNobleBle(nobleBle.disconnect(id));
|
|
467
467
|
}
|
|
468
|
+
this.cleanupDeviceState(id);
|
|
468
469
|
} catch (error) {
|
|
469
470
|
this.Log?.error('[Electron BLE] release failed:', error);
|
|
470
471
|
this.cleanupDeviceState(id);
|
|
@@ -490,19 +491,16 @@ export default class ElectronBleTransport {
|
|
|
490
491
|
}
|
|
491
492
|
return;
|
|
492
493
|
}
|
|
493
|
-
await release(id, keepSession);
|
|
494
|
+
await invokeNobleBle(release(id, keepSession));
|
|
494
495
|
} catch (error) {
|
|
495
496
|
this.Log?.error('[Electron BLE] logical release failed:', error);
|
|
496
497
|
this.cleanupDeviceState(id);
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
private createProtocolMismatchError(expected: ProtocolType
|
|
501
|
-
// A generic Ping miss is not a bond failure. Only a later miss after this
|
|
502
|
-
// endpoint already answered V2, or a native encryption/pairing error, is.
|
|
503
|
-
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
501
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
504
502
|
return ERRORS.TypedError(
|
|
505
|
-
|
|
503
|
+
HardwareErrorCode.RuntimeError,
|
|
506
504
|
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
507
505
|
);
|
|
508
506
|
}
|
|
@@ -542,13 +540,12 @@ export default class ElectronBleTransport {
|
|
|
542
540
|
}
|
|
543
541
|
|
|
544
542
|
if (expectedProtocol === 'V2') {
|
|
545
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
543
|
+
if (await this.probeProtocolV2(uuid, expectedProtocol)) {
|
|
546
544
|
this.deviceProtocol.set(uuid, 'V2');
|
|
547
|
-
this.confirmedProtocolV2.add(uuid);
|
|
548
545
|
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
549
546
|
return 'V2';
|
|
550
547
|
}
|
|
551
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
548
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
552
549
|
}
|
|
553
550
|
|
|
554
551
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
@@ -567,9 +564,6 @@ export default class ElectronBleTransport {
|
|
|
567
564
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
568
565
|
if (detected) {
|
|
569
566
|
this.deviceProtocol.set(uuid, protocol);
|
|
570
|
-
if (protocol === 'V2') {
|
|
571
|
-
this.confirmedProtocolV2.add(uuid);
|
|
572
|
-
}
|
|
573
567
|
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
574
568
|
return protocol;
|
|
575
569
|
}
|
|
@@ -640,7 +634,7 @@ export default class ElectronBleTransport {
|
|
|
640
634
|
}
|
|
641
635
|
}
|
|
642
636
|
|
|
643
|
-
private async probeProtocolV2(uuid: string) {
|
|
637
|
+
private async probeProtocolV2(uuid: string, expectedProtocol?: ProtocolType) {
|
|
644
638
|
if (!this._messages || !this._messagesV2) {
|
|
645
639
|
return false;
|
|
646
640
|
}
|
|
@@ -656,8 +650,11 @@ export default class ElectronBleTransport {
|
|
|
656
650
|
this.v2Assemblers.get(uuid)?.reset();
|
|
657
651
|
this.resetProtocolV2Frames(uuid);
|
|
658
652
|
},
|
|
653
|
+
// A declared V2 protocol needs no fallback; preserve the actual link failure.
|
|
659
654
|
shouldRethrow: error =>
|
|
660
|
-
|
|
655
|
+
expectedProtocol === 'V2' ||
|
|
656
|
+
isBleStaleBondHardwareError(error) ||
|
|
657
|
+
isProtocolV2LinkDisabledError(error),
|
|
661
658
|
});
|
|
662
659
|
if (!detected) {
|
|
663
660
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -672,18 +669,15 @@ export default class ElectronBleTransport {
|
|
|
672
669
|
}
|
|
673
670
|
|
|
674
671
|
try {
|
|
675
|
-
await nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
672
|
+
await invokeNobleBle(nobleBle.write(uuid, hexData, { pacingDelayMs: 0 }));
|
|
676
673
|
} catch (error) {
|
|
677
|
-
|
|
678
|
-
if (staleBondError) {
|
|
679
|
-
throw staleBondError;
|
|
680
|
-
}
|
|
681
|
-
throw error;
|
|
674
|
+
this.handleBluetoothError(error);
|
|
682
675
|
}
|
|
683
676
|
}
|
|
684
677
|
|
|
685
678
|
private async refreshBlePacketCapacity(uuid: string): Promise<void> {
|
|
686
|
-
const
|
|
679
|
+
const nobleBle = window.desktopApi?.nobleBle;
|
|
680
|
+
const device = nobleBle ? await invokeNobleBle(nobleBle.getDevice(uuid)) : undefined;
|
|
687
681
|
this.updateBlePacketCapacity(uuid, device?.mtu);
|
|
688
682
|
}
|
|
689
683
|
|
|
@@ -941,7 +935,7 @@ export default class ElectronBleTransport {
|
|
|
941
935
|
if (hexString.length === 0) {
|
|
942
936
|
throw new Error(`Buffer ${i + 1} is empty`);
|
|
943
937
|
}
|
|
944
|
-
await window.desktopApi.nobleBle.write(uuid, hexString);
|
|
938
|
+
await invokeNobleBle(window.desktopApi.nobleBle.write(uuid, hexString));
|
|
945
939
|
}
|
|
946
940
|
|
|
947
941
|
const response = await Promise.race([
|
|
@@ -979,7 +973,7 @@ export default class ElectronBleTransport {
|
|
|
979
973
|
await this.releaseNative(uuid);
|
|
980
974
|
}
|
|
981
975
|
}
|
|
982
|
-
throw e;
|
|
976
|
+
throw this.normalizeBluetoothError(e);
|
|
983
977
|
} finally {
|
|
984
978
|
if (timeout) clearTimeout(timeout);
|
|
985
979
|
if (this.runPromise === runPromise) {
|