@onekeyfe/hd-transport-react-native 1.2.2-alpha.100 → 1.2.2-alpha.101
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/bleStaleBond.d.ts +5 -0
- package/dist/bleStaleBond.d.ts.map +1 -0
- package/dist/bleStrategy.d.ts +1 -1
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -70
- package/package.json +5 -5
- package/src/__tests__/bleStaleBond.test.ts +47 -0
- package/src/__tests__/bleStrategy.test.ts +2 -3
- package/src/__tests__/protocolV2Link.test.ts +239 -16
- package/src/bleStaleBond.ts +64 -0
- package/src/bleStrategy.ts +1 -3
- package/src/index.ts +220 -74
|
@@ -90,7 +90,7 @@ describe('React Native BLE strategy', () => {
|
|
|
90
90
|
).toBe(182);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
test('uses withoutResponse
|
|
93
|
+
test('uses withoutResponse by default unless explicitly overridden', () => {
|
|
94
94
|
const characteristic = {
|
|
95
95
|
isWritableWithResponse: true,
|
|
96
96
|
isWritableWithoutResponse: true,
|
|
@@ -99,8 +99,7 @@ describe('React Native BLE strategy', () => {
|
|
|
99
99
|
expect(
|
|
100
100
|
shouldWriteProtocolV2WithResponse({
|
|
101
101
|
platform: 'ios',
|
|
102
|
-
highThroughput:
|
|
103
|
-
requestedWithResponse: false,
|
|
102
|
+
highThroughput: false,
|
|
104
103
|
characteristic,
|
|
105
104
|
})
|
|
106
105
|
).toBe(false);
|
|
@@ -109,9 +109,11 @@ const schemas = {
|
|
|
109
109
|
const createHarness = ({
|
|
110
110
|
deviceName = 'OneKey Pro 2',
|
|
111
111
|
isWritableWithResponse = true,
|
|
112
|
+
monitorError,
|
|
112
113
|
}: {
|
|
113
114
|
deviceName?: string;
|
|
114
115
|
isWritableWithResponse?: boolean;
|
|
116
|
+
monitorError?: (Error & { reason?: string; attErrorCode?: number; iosErrorCode?: number }) | null;
|
|
115
117
|
} = {}) => {
|
|
116
118
|
const uuid = 'rn-pro2-id';
|
|
117
119
|
const sentSeqs: number[] = [];
|
|
@@ -130,6 +132,9 @@ const createHarness = ({
|
|
|
130
132
|
isNotifiable: true,
|
|
131
133
|
monitor: jest.fn(callback => {
|
|
132
134
|
notifyCallback = callback;
|
|
135
|
+
if (monitorError) {
|
|
136
|
+
queueMicrotask(() => callback(monitorError, null));
|
|
137
|
+
}
|
|
133
138
|
return { remove: jest.fn() };
|
|
134
139
|
}),
|
|
135
140
|
};
|
|
@@ -355,6 +360,87 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
355
360
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
356
361
|
});
|
|
357
362
|
|
|
363
|
+
test('connects the Android GATT link before starting system bonding', async () => {
|
|
364
|
+
setPlatformOS('android');
|
|
365
|
+
const { transport, uuid, device } = createHarness();
|
|
366
|
+
const operationOrder: string[] = [];
|
|
367
|
+
const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
|
|
368
|
+
|
|
369
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
370
|
+
device.connect = jest.fn(() => {
|
|
371
|
+
operationOrder.push('connect');
|
|
372
|
+
return Promise.resolve(device);
|
|
373
|
+
});
|
|
374
|
+
pairDeviceMock.mockImplementationOnce(() => {
|
|
375
|
+
operationOrder.push('bond');
|
|
376
|
+
return Promise.resolve({ bonded: true, bonding: false });
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
380
|
+
uuid,
|
|
381
|
+
protocolType: 'V2',
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(operationOrder).toEqual(['connect', 'bond']);
|
|
385
|
+
await transport.release(uuid, true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test.each([
|
|
389
|
+
[
|
|
390
|
+
'ios',
|
|
391
|
+
'OneKey Neo',
|
|
392
|
+
{
|
|
393
|
+
iosErrorCode: 14,
|
|
394
|
+
reason: 'Peer removed pairing information',
|
|
395
|
+
},
|
|
396
|
+
HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
397
|
+
],
|
|
398
|
+
[
|
|
399
|
+
'android',
|
|
400
|
+
'OneKey Pro 2',
|
|
401
|
+
{
|
|
402
|
+
androidErrorCode: 5,
|
|
403
|
+
reason: 'Connection state changed with status 5',
|
|
404
|
+
},
|
|
405
|
+
HardwareErrorCode.BleDeviceBondError,
|
|
406
|
+
],
|
|
407
|
+
] as const)(
|
|
408
|
+
'maps a %s %s stale bond during connect before protocol detection',
|
|
409
|
+
async (platform, deviceName, nativeError, errorCode) => {
|
|
410
|
+
setPlatformOS(platform);
|
|
411
|
+
const { transport, uuid, device } = createHarness({ deviceName });
|
|
412
|
+
const BleErrorMock = jest.requireMock('react-native-ble-plx').BleError as new (
|
|
413
|
+
message: string
|
|
414
|
+
) => Error;
|
|
415
|
+
const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
|
|
416
|
+
pairDeviceMock.mockClear();
|
|
417
|
+
|
|
418
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
419
|
+
device.connect = jest
|
|
420
|
+
.fn()
|
|
421
|
+
.mockRejectedValue(Object.assign(new BleErrorMock(nativeError.reason), nativeError));
|
|
422
|
+
|
|
423
|
+
await expect(transport.acquire({ uuid })).rejects.toMatchObject({ errorCode });
|
|
424
|
+
expect(pairDeviceMock).not.toHaveBeenCalled();
|
|
425
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
test('closes the Android GATT link when system bonding fails', async () => {
|
|
430
|
+
setPlatformOS('android');
|
|
431
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
432
|
+
const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
|
|
433
|
+
|
|
434
|
+
pairDeviceMock.mockRejectedValueOnce(new Error('bonding canceled'));
|
|
435
|
+
|
|
436
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toThrow(
|
|
437
|
+
'bonding canceled'
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
441
|
+
expect(device.cancelConnection).toHaveBeenCalledTimes(1);
|
|
442
|
+
});
|
|
443
|
+
|
|
358
444
|
test('uses withResponse for consecutive iOS Protocol V1 control commands without releasing', async () => {
|
|
359
445
|
const { transport, uuid, writeCharacteristic } = createV1Harness({
|
|
360
446
|
respondOnWriteCount: [1, 2],
|
|
@@ -405,6 +491,22 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
405
491
|
await transport.release(uuid, true);
|
|
406
492
|
});
|
|
407
493
|
|
|
494
|
+
test('keeps native stale-bond write mapping out of Protocol V1 calls', async () => {
|
|
495
|
+
const { transport, uuid, writeCharacteristic } = createV1Harness();
|
|
496
|
+
const nativeError = Object.assign(new Error('Encryption is insufficient'), {
|
|
497
|
+
attErrorCode: 15,
|
|
498
|
+
reason: 'Encryption is insufficient',
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
502
|
+
writeCharacteristic.writeWithResponse.mockRejectedValueOnce(nativeError);
|
|
503
|
+
|
|
504
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
|
|
505
|
+
errorCode: HardwareErrorCode.BleWriteCharacteristicError,
|
|
506
|
+
});
|
|
507
|
+
await transport.release(uuid, true);
|
|
508
|
+
});
|
|
509
|
+
|
|
408
510
|
test('detects Protocol V2 on iOS without using the BLE name as a protocol hint', async () => {
|
|
409
511
|
const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
|
|
410
512
|
deviceName: 'Pro2 6E9E',
|
|
@@ -415,7 +517,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
415
517
|
protocolType: 'V2',
|
|
416
518
|
});
|
|
417
519
|
expect(device.requestMTU).toHaveBeenCalledWith(247);
|
|
418
|
-
expect(writeCharacteristic.writeWithResponse
|
|
520
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
521
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
|
|
419
522
|
|
|
420
523
|
await expect(
|
|
421
524
|
transport.call(uuid, 'Ping', { message: 'first-core-command' })
|
|
@@ -424,6 +527,93 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
424
527
|
await transport.release(uuid, true);
|
|
425
528
|
});
|
|
426
529
|
|
|
530
|
+
test('physically refreshes an uncached Protocol V2 firmware install link without Ping', async () => {
|
|
531
|
+
const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
|
|
532
|
+
const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
533
|
+
(transport as any).sessionProtocols.set(uuid, 'V2');
|
|
534
|
+
device.connect = jest.fn().mockResolvedValue(device);
|
|
535
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
536
|
+
|
|
537
|
+
await expect(
|
|
538
|
+
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
539
|
+
).resolves.toEqual({
|
|
540
|
+
uuid,
|
|
541
|
+
protocolType: 'V2',
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
545
|
+
expect(device.cancelConnection).not.toHaveBeenCalled();
|
|
546
|
+
expect(device.connect).toHaveBeenCalled();
|
|
547
|
+
expect(probeProtocolV2).not.toHaveBeenCalled();
|
|
548
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
549
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
550
|
+
expect(transport.getProtocolType(uuid)).toBe('V2');
|
|
551
|
+
await transport.release(uuid, true);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('refreshes a cached firmware install connection instead of trusting GATT state', async () => {
|
|
555
|
+
const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
|
|
556
|
+
const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
557
|
+
|
|
558
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
559
|
+
device.connect = jest.fn().mockResolvedValue(device);
|
|
560
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
561
|
+
writeCharacteristic.writeWithResponse.mockClear();
|
|
562
|
+
writeCharacteristic.writeWithoutResponse.mockClear();
|
|
563
|
+
|
|
564
|
+
await expect(
|
|
565
|
+
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
566
|
+
).resolves.toEqual({
|
|
567
|
+
uuid,
|
|
568
|
+
protocolType: 'V2',
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
572
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
573
|
+
expect(device.connect).toHaveBeenCalled();
|
|
574
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
575
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
576
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
577
|
+
await transport.release(uuid, true);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test('rejects no-probe acquire before the BLE endpoint has confirmed a protocol', async () => {
|
|
581
|
+
const { transport, uuid } = createHarness();
|
|
582
|
+
|
|
583
|
+
await expect(
|
|
584
|
+
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
585
|
+
).rejects.toThrow('previously confirmed protocol');
|
|
586
|
+
|
|
587
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test('keeps confirmed V2 authorization across a BLE manager reset', async () => {
|
|
591
|
+
const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
|
|
592
|
+
const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
593
|
+
|
|
594
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
595
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
596
|
+
|
|
597
|
+
(transport as any).resetPlxManager();
|
|
598
|
+
transport.blePlxManager = bleManager as any;
|
|
599
|
+
device.connect = jest.fn().mockResolvedValue(device);
|
|
600
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
601
|
+
writeCharacteristic.writeWithResponse.mockClear();
|
|
602
|
+
writeCharacteristic.writeWithoutResponse.mockClear();
|
|
603
|
+
|
|
604
|
+
await expect(
|
|
605
|
+
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
606
|
+
).resolves.toEqual({
|
|
607
|
+
uuid,
|
|
608
|
+
protocolType: 'V2',
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
612
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
613
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
614
|
+
await transport.release(uuid, true);
|
|
615
|
+
});
|
|
616
|
+
|
|
427
617
|
test.each(['ios', 'android'] as const)(
|
|
428
618
|
'keeps a first expected Protocol V2 probe miss retryable on %s',
|
|
429
619
|
async platform => {
|
|
@@ -459,8 +649,37 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
459
649
|
}
|
|
460
650
|
);
|
|
461
651
|
|
|
652
|
+
test.each([
|
|
653
|
+
[
|
|
654
|
+
'Encryption is insufficient',
|
|
655
|
+
{ reason: 'Encryption is insufficient', attErrorCode: 15 },
|
|
656
|
+
HardwareErrorCode.BleDeviceBondError,
|
|
657
|
+
],
|
|
658
|
+
[
|
|
659
|
+
'Peer removed pairing information',
|
|
660
|
+
{ reason: 'Peer removed pairing information', iosErrorCode: 14 },
|
|
661
|
+
HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
662
|
+
],
|
|
663
|
+
] as const)(
|
|
664
|
+
'fails Protocol V2 acquire immediately on %s instead of waiting for Ping',
|
|
665
|
+
async (_label, nativeError, errorCode) => {
|
|
666
|
+
const { transport, uuid, device } = createHarness({
|
|
667
|
+
monitorError: Object.assign(new Error(nativeError.reason), nativeError),
|
|
668
|
+
});
|
|
669
|
+
const probe = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
670
|
+
|
|
671
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
|
|
672
|
+
errorCode,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
expect(probe).not.toHaveBeenCalled();
|
|
676
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
677
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
|
|
462
681
|
test.each(['ios', 'android'] as const)(
|
|
463
|
-
'
|
|
682
|
+
'keeps a confirmed Protocol V2 probe miss retryable on %s without native bond evidence',
|
|
464
683
|
async platform => {
|
|
465
684
|
setPlatformOS(platform);
|
|
466
685
|
const { transport, uuid, device } = createHarness();
|
|
@@ -469,10 +688,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
469
688
|
jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
|
|
470
689
|
|
|
471
690
|
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
|
|
472
|
-
errorCode: HardwareErrorCode.
|
|
691
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
473
692
|
});
|
|
474
693
|
|
|
475
|
-
expect(device.cancelConnection).toHaveBeenCalled();
|
|
694
|
+
expect(device.cancelConnection).not.toHaveBeenCalled();
|
|
476
695
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
477
696
|
}
|
|
478
697
|
);
|
|
@@ -658,7 +877,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
658
877
|
});
|
|
659
878
|
expect(device.requestMTU).toHaveBeenCalledTimes(3);
|
|
660
879
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBeUndefined();
|
|
661
|
-
expect(writeCharacteristic.
|
|
880
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
|
|
662
881
|
await transport.release(uuid, true);
|
|
663
882
|
});
|
|
664
883
|
|
|
@@ -667,6 +886,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
667
886
|
device.mtu = undefined;
|
|
668
887
|
|
|
669
888
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
889
|
+
const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
|
|
670
890
|
device.requestMTU.mockImplementationOnce(() => {
|
|
671
891
|
device.mtu = 247;
|
|
672
892
|
return Promise.resolve(device);
|
|
@@ -674,7 +894,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
674
894
|
|
|
675
895
|
await expect(transport.call(uuid, 'FileWrite', {})).resolves.toBeDefined();
|
|
676
896
|
expect(device.requestMTU).toHaveBeenCalledTimes(4);
|
|
677
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
897
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
898
|
+
writesBeforeFileWrite + 1
|
|
899
|
+
);
|
|
678
900
|
await transport.release(uuid, true);
|
|
679
901
|
});
|
|
680
902
|
|
|
@@ -683,12 +905,13 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
683
905
|
device.mtu = undefined;
|
|
684
906
|
|
|
685
907
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
908
|
+
const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
|
|
686
909
|
|
|
687
910
|
await expect(transport.call(uuid, 'FileWrite', {})).rejects.toMatchObject({
|
|
688
911
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
689
912
|
});
|
|
690
913
|
expect(device.requestMTU).toHaveBeenCalledTimes(4);
|
|
691
|
-
expect(writeCharacteristic.writeWithoutResponse).
|
|
914
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(writesBeforeFileWrite);
|
|
692
915
|
await transport.release(uuid, true);
|
|
693
916
|
});
|
|
694
917
|
|
|
@@ -808,19 +1031,19 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
808
1031
|
await transport.release(uuid, true);
|
|
809
1032
|
});
|
|
810
1033
|
|
|
811
|
-
test('uses
|
|
1034
|
+
test('uses withoutResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
|
|
812
1035
|
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
813
1036
|
|
|
814
1037
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
815
1038
|
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
816
|
-
expect(writeCharacteristic.writeWithoutResponse).
|
|
817
|
-
expect(writeCharacteristic.writeWithResponse).
|
|
1039
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
1040
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
818
1041
|
|
|
819
1042
|
await transport.call(uuid, 'DeviceInfoGet', {});
|
|
820
1043
|
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
821
1044
|
|
|
822
|
-
expect(writeCharacteristic.
|
|
823
|
-
expect(writeCharacteristic.
|
|
1045
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
|
|
1046
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
824
1047
|
expect(releaseNative).not.toHaveBeenCalled();
|
|
825
1048
|
|
|
826
1049
|
await transport.release(uuid, true);
|
|
@@ -833,8 +1056,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
833
1056
|
|
|
834
1057
|
await transport.call(uuid, 'FileWrite', {});
|
|
835
1058
|
await transport.call(uuid, 'FileWrite', {});
|
|
836
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
837
|
-
expect(writeCharacteristic.writeWithResponse).
|
|
1059
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
|
|
1060
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
838
1061
|
expect(
|
|
839
1062
|
logger.debug.mock.calls.filter(
|
|
840
1063
|
([message]) =>
|
|
@@ -867,8 +1090,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
867
1090
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
868
1091
|
|
|
869
1092
|
await transport.call(uuid, 'FileWrite', {}, { writeWithResponse: true });
|
|
870
|
-
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(
|
|
871
|
-
expect(writeCharacteristic.writeWithoutResponse).
|
|
1093
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
1094
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
872
1095
|
await transport.release(uuid, true);
|
|
873
1096
|
});
|
|
874
1097
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERRORS,
|
|
3
|
+
HardwareErrorCode,
|
|
4
|
+
isBleStaleBondErrorText,
|
|
5
|
+
isBleStaleBondHardwareError,
|
|
6
|
+
} from '@onekeyfe/hd-shared';
|
|
7
|
+
|
|
8
|
+
export { isBleStaleBondHardwareError };
|
|
9
|
+
|
|
10
|
+
const ATT_INSUFFICIENT_AUTHENTICATION = 5;
|
|
11
|
+
const ATT_INSUFFICIENT_ENCRYPTION = 15;
|
|
12
|
+
const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
|
|
13
|
+
|
|
14
|
+
type NativeBleErrorFields = {
|
|
15
|
+
attErrorCode?: unknown;
|
|
16
|
+
androidErrorCode?: unknown;
|
|
17
|
+
iosErrorCode?: unknown;
|
|
18
|
+
reason?: unknown;
|
|
19
|
+
message?: unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const nativeErrorText = (error: NativeBleErrorFields) =>
|
|
23
|
+
[error.reason, error.message]
|
|
24
|
+
.filter((value): value is string => typeof value === 'string')
|
|
25
|
+
.join(' ');
|
|
26
|
+
|
|
27
|
+
export const isNativeBleStaleBondError = (error: unknown): boolean => {
|
|
28
|
+
if (!error || typeof error !== 'object') {
|
|
29
|
+
return typeof error === 'string' ? isBleStaleBondErrorText(error) : false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const nativeError = error as NativeBleErrorFields;
|
|
33
|
+
if (
|
|
34
|
+
nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
|
|
35
|
+
nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
|
|
36
|
+
nativeError.androidErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
|
|
37
|
+
nativeError.androidErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
|
|
38
|
+
nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION
|
|
39
|
+
) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return isBleStaleBondErrorText(nativeErrorText(nativeError));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const toBleStaleBondHardwareError = (error: unknown) => {
|
|
47
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
48
|
+
return error as Error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const nativeError = (error ?? {}) as NativeBleErrorFields;
|
|
52
|
+
const text = nativeErrorText(nativeError);
|
|
53
|
+
const normalizedText = text.toLowerCase();
|
|
54
|
+
const peerRemoved =
|
|
55
|
+
nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
|
|
56
|
+
normalizedText.includes('peer removed pairing information');
|
|
57
|
+
|
|
58
|
+
return ERRORS.TypedError(
|
|
59
|
+
peerRemoved
|
|
60
|
+
? HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
61
|
+
: HardwareErrorCode.BleDeviceBondError,
|
|
62
|
+
text || undefined
|
|
63
|
+
);
|
|
64
|
+
};
|
package/src/bleStrategy.ts
CHANGED
|
@@ -34,8 +34,6 @@ export function shouldRefreshNegotiatedMtu(mtu?: number | null) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function shouldWriteProtocolV2WithResponse({
|
|
37
|
-
platform,
|
|
38
|
-
highThroughput,
|
|
39
37
|
requestedWithResponse,
|
|
40
38
|
characteristic,
|
|
41
39
|
}: {
|
|
@@ -46,5 +44,5 @@ export function shouldWriteProtocolV2WithResponse({
|
|
|
46
44
|
}) {
|
|
47
45
|
if (!characteristic.isWritableWithResponse) return false;
|
|
48
46
|
if (!characteristic.isWritableWithoutResponse) return true;
|
|
49
|
-
return requestedWithResponse === true
|
|
47
|
+
return requestedWithResponse === true;
|
|
50
48
|
}
|