@onekeyfe/hd-transport-web-device 1.2.0-alpha.98 → 1.2.0
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/__tests__/electron-ble-transport.test.ts +289 -32
- package/__tests__/webusb-protocol-cache.test.ts +276 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +22 -8
- package/dist/electron-ble-transport.d.ts +6 -1
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +64 -3
- package/dist/index.js +232 -110
- package/dist/webusb.d.ts +11 -1
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +172 -85
- package/src/webusb.ts +201 -33
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
|
|
2
|
-
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { EBleDisconnectReason, HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
|
|
5
5
|
import ElectronBleTransport from '../src/electron-ble-transport';
|
|
@@ -73,8 +73,26 @@ const protocolV2Schema = {
|
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
|
+
Failure: {
|
|
77
|
+
fields: {
|
|
78
|
+
code: {
|
|
79
|
+
type: 'FailureType',
|
|
80
|
+
id: 1,
|
|
81
|
+
},
|
|
82
|
+
message: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
id: 2,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
FailureType: {
|
|
89
|
+
values: {
|
|
90
|
+
Failure_ProcessError: 5,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
76
93
|
MessageType: {
|
|
77
94
|
values: {
|
|
95
|
+
MessageType_Failure: 3,
|
|
78
96
|
MessageType_ProtocolInfoRequest: 60200,
|
|
79
97
|
MessageType_ProtocolInfo: 60201,
|
|
80
98
|
MessageType_Ping: 60206,
|
|
@@ -134,6 +152,30 @@ const configureTransport = (
|
|
|
134
152
|
return transport;
|
|
135
153
|
};
|
|
136
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Wire the mock so acquire()'s Protocol V2 probe gets an answer. Without it
|
|
157
|
+
* detectProtocol throws and the test never reaches the disconnect behaviour.
|
|
158
|
+
*/
|
|
159
|
+
const echoProtocolV2 = (nobleBle: ReturnType<typeof createNobleBle>, deviceId: string) => {
|
|
160
|
+
let notificationHandler: ((id: string, data: string) => void) | undefined;
|
|
161
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
162
|
+
notificationHandler = handler;
|
|
163
|
+
return jest.fn();
|
|
164
|
+
});
|
|
165
|
+
let responseSeq = 0;
|
|
166
|
+
nobleBle.write.mockImplementation(() => {
|
|
167
|
+
responseSeq += 1;
|
|
168
|
+
const response = ProtocolV2.encodeFrame(
|
|
169
|
+
schemas,
|
|
170
|
+
'Success',
|
|
171
|
+
{ message: 'ok' },
|
|
172
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
173
|
+
);
|
|
174
|
+
setTimeout(() => notificationHandler?.(deviceId, bytesToHex(response)), 0);
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
137
179
|
describe('ElectronBleTransport protocol detection', () => {
|
|
138
180
|
afterEach(() => {
|
|
139
181
|
delete (global as any).window;
|
|
@@ -294,12 +336,16 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
294
336
|
})
|
|
295
337
|
);
|
|
296
338
|
expect(transport.getProtocolType(device.id)).toBe('V2');
|
|
339
|
+
expect(nobleBle.connect).toHaveBeenCalledTimes(1);
|
|
340
|
+
expect(nobleBle.subscribe).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
342
|
+
expect(nobleBle.disconnect).not.toHaveBeenCalled();
|
|
297
343
|
} finally {
|
|
298
344
|
await transport.release(device.id);
|
|
299
345
|
}
|
|
300
346
|
});
|
|
301
347
|
|
|
302
|
-
test('reconnects Protocol V1
|
|
348
|
+
test('reconnects a declared Protocol V1 device without probing again', async () => {
|
|
303
349
|
const device = { id: 'classic-id', name: 'OneKey Classic' };
|
|
304
350
|
const nobleBle = createNobleBle(device);
|
|
305
351
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -332,7 +378,10 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
332
378
|
uuid: device.id,
|
|
333
379
|
})
|
|
334
380
|
);
|
|
335
|
-
|
|
381
|
+
// The first acquire probes because the protocol is unknown; the second
|
|
382
|
+
// declares V1 and must send nothing at all, leaving the first frame on
|
|
383
|
+
// the link to Core — which is what carries the wallet session.
|
|
384
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
336
385
|
expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
|
|
337
386
|
expect(protocolV2Writer).not.toHaveBeenCalled();
|
|
338
387
|
} finally {
|
|
@@ -345,19 +394,13 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
345
394
|
const nobleBle = createNobleBle(device);
|
|
346
395
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
347
396
|
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
348
|
-
let writeCount = 0;
|
|
349
|
-
|
|
350
397
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
351
398
|
notificationHandler = handler;
|
|
352
399
|
return jest.fn();
|
|
353
400
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
358
|
-
}
|
|
359
|
-
return Promise.resolve();
|
|
360
|
-
});
|
|
401
|
+
// A declared V1 acquire writes nothing, so every write here belongs to the
|
|
402
|
+
// call under test and none of them is answered.
|
|
403
|
+
nobleBle.write.mockImplementation(() => Promise.resolve());
|
|
361
404
|
const bleTransport = configureTransport(nobleBle);
|
|
362
405
|
|
|
363
406
|
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
@@ -436,7 +479,133 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
436
479
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
437
480
|
});
|
|
438
481
|
|
|
439
|
-
test('
|
|
482
|
+
test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
|
|
483
|
+
const device = { id: 'usb-priority-pro2-id', name: 'OneKey Pro 2' };
|
|
484
|
+
const nobleBle = createNobleBle(device);
|
|
485
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
486
|
+
|
|
487
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
488
|
+
notificationHandler = handler;
|
|
489
|
+
return jest.fn();
|
|
490
|
+
});
|
|
491
|
+
nobleBle.write.mockImplementation(() => {
|
|
492
|
+
const response = ProtocolV2.encodeFrame(
|
|
493
|
+
schemas,
|
|
494
|
+
'Failure',
|
|
495
|
+
{ code: 5, message: 'link disabled' },
|
|
496
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
497
|
+
);
|
|
498
|
+
const splitAt = 4;
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
notificationHandler?.(device.id, bytesToHex(response.subarray(0, splitAt)));
|
|
501
|
+
notificationHandler?.(device.id, bytesToHex(response.subarray(splitAt)));
|
|
502
|
+
}, 0);
|
|
503
|
+
return Promise.resolve();
|
|
504
|
+
});
|
|
505
|
+
const transport = configureTransport(nobleBle);
|
|
506
|
+
|
|
507
|
+
await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
|
|
508
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
509
|
+
failureCode: 'Failure_ProcessError',
|
|
510
|
+
firmwareMessage: 'link disabled',
|
|
511
|
+
});
|
|
512
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
513
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
514
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('keeps a first expected Protocol V2 probe miss retryable', async () => {
|
|
518
|
+
const device = { id: 'first-v2-id', name: 'OneKey Pro 2' };
|
|
519
|
+
const nobleBle = createNobleBle(device);
|
|
520
|
+
const transport = configureTransport(nobleBle);
|
|
521
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
|
|
522
|
+
|
|
523
|
+
await expect(
|
|
524
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
525
|
+
).rejects.toMatchObject({
|
|
526
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
expect(nobleBle.connect).toHaveBeenCalledTimes(1);
|
|
530
|
+
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test('keeps a second expected Protocol V2 probe miss retryable', async () => {
|
|
534
|
+
const device = { id: 'retry-pro2-id', name: 'OneKey Pro 2' };
|
|
535
|
+
const nobleBle = createNobleBle(device);
|
|
536
|
+
const transport = configureTransport(nobleBle);
|
|
537
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
|
|
538
|
+
|
|
539
|
+
await expect(
|
|
540
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
541
|
+
).rejects.toMatchObject({
|
|
542
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
543
|
+
});
|
|
544
|
+
await expect(
|
|
545
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
546
|
+
).rejects.toMatchObject({
|
|
547
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
|
|
554
|
+
const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
|
|
555
|
+
const nobleBle = createNobleBle(device);
|
|
556
|
+
nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
|
|
557
|
+
const transport = configureTransport(nobleBle);
|
|
558
|
+
const probe = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
559
|
+
|
|
560
|
+
await expect(
|
|
561
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
562
|
+
).rejects.toMatchObject({
|
|
563
|
+
errorCode: HardwareErrorCode.BleDeviceBondError,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
expect(probe).not.toHaveBeenCalled();
|
|
567
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
568
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test('keeps stale-bond subscribe mapping out of Protocol V1 acquire', async () => {
|
|
572
|
+
const device = { id: 'classic-v1-id', name: 'OneKey Classic' };
|
|
573
|
+
const nobleBle = createNobleBle(device);
|
|
574
|
+
nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
|
|
575
|
+
const transport = configureTransport(nobleBle);
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
579
|
+
throw new Error('Expected Protocol V1 acquire to fail');
|
|
580
|
+
} catch (error) {
|
|
581
|
+
expect(error).toBeInstanceOf(Error);
|
|
582
|
+
expect((error as Error).message).toBe('Encryption is insufficient');
|
|
583
|
+
expect((error as { errorCode?: unknown }).errorCode).toBeUndefined();
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('reports a stale bond when a previously confirmed Protocol V2 device stops responding', async () => {
|
|
588
|
+
const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
|
|
589
|
+
const nobleBle = createNobleBle(device);
|
|
590
|
+
const transport = configureTransport(nobleBle);
|
|
591
|
+
const probe = jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(true);
|
|
592
|
+
|
|
593
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
594
|
+
await transport.release(device.id);
|
|
595
|
+
probe.mockResolvedValue(false);
|
|
596
|
+
|
|
597
|
+
await expect(
|
|
598
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
599
|
+
).rejects.toMatchObject({
|
|
600
|
+
errorCode: HardwareErrorCode.BleDeviceBondError,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
604
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
605
|
+
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('does not take a Protocol V2 hint from the BLE name', async () => {
|
|
440
609
|
const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
|
|
441
610
|
const nobleBle = createNobleBle(device);
|
|
442
611
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -466,15 +635,15 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
466
635
|
protocolType: 'V2',
|
|
467
636
|
})
|
|
468
637
|
);
|
|
469
|
-
expect(nobleBle.write).
|
|
638
|
+
expect(nobleBle.write.mock.calls.length).toBeGreaterThan(1);
|
|
470
639
|
expect(transport.getProtocolType(device.id)).toBe('V2');
|
|
471
640
|
await expect(transport.call(device.id, 'Ping', { message: 'after-probe' })).resolves.toEqual({
|
|
472
641
|
type: 'Success',
|
|
473
642
|
message: { message: 'ok' },
|
|
474
643
|
});
|
|
475
|
-
const sentSeqs = nobleBle.write.mock.calls
|
|
476
|
-
Number.parseInt(hex.slice(12, 14), 16)
|
|
477
|
-
|
|
644
|
+
const sentSeqs = nobleBle.write.mock.calls
|
|
645
|
+
.map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
|
|
646
|
+
.filter(seq => seq > 0);
|
|
478
647
|
expect(sentSeqs).toEqual([1, 2]);
|
|
479
648
|
} finally {
|
|
480
649
|
await transport.release(device.id);
|
|
@@ -554,30 +723,28 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
554
723
|
message: { message: 'ok' },
|
|
555
724
|
});
|
|
556
725
|
|
|
557
|
-
const sentSeqs = nobleBle.write.mock.calls
|
|
558
|
-
Number.parseInt(hex.slice(12, 14), 16)
|
|
559
|
-
|
|
726
|
+
const sentSeqs = nobleBle.write.mock.calls
|
|
727
|
+
.map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
|
|
728
|
+
.filter(seq => seq > 0);
|
|
560
729
|
expect(sentSeqs).toEqual([1, 2, 3]);
|
|
561
730
|
} finally {
|
|
562
731
|
await transport.release(device.id);
|
|
563
732
|
}
|
|
564
733
|
});
|
|
565
734
|
|
|
566
|
-
test('
|
|
567
|
-
|
|
735
|
+
test('subscribes to host disconnects once for the transport lifetime', async () => {
|
|
736
|
+
// The previous design registered a listener inside every acquire() and
|
|
737
|
+
// overwrote the cleanup entry without disposing the old one, so each
|
|
738
|
+
// acquire leaked a live handler and a delayed event from a superseded
|
|
739
|
+
// connection could tear down the current one. One transport-lifetime
|
|
740
|
+
// subscription removes that whole class of bug.
|
|
741
|
+
const device = { id: 'single-subscription-pro2-id', name: 'OneKey Pro 2' };
|
|
568
742
|
const nobleBle = createNobleBle(device);
|
|
569
743
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
570
|
-
const disconnectHandlers: Array<
|
|
571
|
-
(disconnectedDevice: { id: string; name: string | null }) => void
|
|
572
|
-
> = [];
|
|
573
744
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
574
745
|
notificationHandler = handler;
|
|
575
746
|
return jest.fn();
|
|
576
747
|
});
|
|
577
|
-
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
578
|
-
disconnectHandlers.push(handler);
|
|
579
|
-
return jest.fn();
|
|
580
|
-
});
|
|
581
748
|
let responseSeq = 0;
|
|
582
749
|
nobleBle.write.mockImplementation(() => {
|
|
583
750
|
responseSeq += 1;
|
|
@@ -593,14 +760,15 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
593
760
|
const bleTransport = configureTransport(nobleBle);
|
|
594
761
|
|
|
595
762
|
try {
|
|
763
|
+
expect(nobleBle.onDeviceDisconnected).toHaveBeenCalledTimes(1);
|
|
764
|
+
|
|
596
765
|
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
597
766
|
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
598
767
|
|
|
599
|
-
|
|
600
|
-
|
|
768
|
+
expect(nobleBle.onDeviceDisconnected).toHaveBeenCalledTimes(1);
|
|
601
769
|
expect(bleTransport.getProtocolType(device.id)).toBe('V2');
|
|
602
770
|
await expect(
|
|
603
|
-
bleTransport.call(device.id, 'Ping', { message: 'after-
|
|
771
|
+
bleTransport.call(device.id, 'Ping', { message: 'after-reacquire' })
|
|
604
772
|
).resolves.toEqual({
|
|
605
773
|
type: 'Success',
|
|
606
774
|
message: { message: 'ok' },
|
|
@@ -610,6 +778,95 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
610
778
|
}
|
|
611
779
|
});
|
|
612
780
|
|
|
781
|
+
test('reports a device that drops after a logical release (OK-60486)', async () => {
|
|
782
|
+
// A logical release keeps the native link alive for the keep-alive window.
|
|
783
|
+
// A drop during that window must still reach consumers, or the UI keeps
|
|
784
|
+
// showing the device as connected forever.
|
|
785
|
+
const device = { id: 'idle-drop-pro2-id', name: 'OneKey Pro 2' };
|
|
786
|
+
const nobleBle = createNobleBle(device);
|
|
787
|
+
const emitter = new EventEmitter();
|
|
788
|
+
let disconnectHandler:
|
|
789
|
+
| ((d: { id: string; name: string; reason?: EBleDisconnectReason }) => void)
|
|
790
|
+
| undefined;
|
|
791
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
792
|
+
disconnectHandler = handler;
|
|
793
|
+
return jest.fn();
|
|
794
|
+
});
|
|
795
|
+
echoProtocolV2(nobleBle, device.id);
|
|
796
|
+
const transportDisconnect = jest.fn();
|
|
797
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
798
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
799
|
+
|
|
800
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
801
|
+
await bleTransport.release(device.id);
|
|
802
|
+
|
|
803
|
+
disconnectHandler?.({ ...device, reason: EBleDisconnectReason.DeviceDisconnected });
|
|
804
|
+
|
|
805
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
806
|
+
id: device.id,
|
|
807
|
+
connectId: device.id,
|
|
808
|
+
name: device.name,
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test('reports an idle keep-alive release as a device disconnect', async () => {
|
|
813
|
+
// The main process reclaims idle links on its own timer. That is still a
|
|
814
|
+
// closed link, and consumers track link liveness, so it is reported like
|
|
815
|
+
// any other drop. Nothing reconnects on its own, so the state settles once
|
|
816
|
+
// until the user acts.
|
|
817
|
+
const device = { id: 'keep-alive-pro2-id', name: 'OneKey Pro 2' };
|
|
818
|
+
const nobleBle = createNobleBle(device);
|
|
819
|
+
const emitter = new EventEmitter();
|
|
820
|
+
let disconnectHandler:
|
|
821
|
+
| ((d: { id: string; name: string; reason?: EBleDisconnectReason }) => void)
|
|
822
|
+
| undefined;
|
|
823
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
824
|
+
disconnectHandler = handler;
|
|
825
|
+
return jest.fn();
|
|
826
|
+
});
|
|
827
|
+
echoProtocolV2(nobleBle, device.id);
|
|
828
|
+
const transportDisconnect = jest.fn();
|
|
829
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
830
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
831
|
+
|
|
832
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
833
|
+
disconnectHandler?.({ ...device, reason: EBleDisconnectReason.IdleKeepAlive });
|
|
834
|
+
|
|
835
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
836
|
+
id: device.id,
|
|
837
|
+
connectId: device.id,
|
|
838
|
+
name: device.name,
|
|
839
|
+
});
|
|
840
|
+
// The link really is gone, so cached link state must be dropped too.
|
|
841
|
+
expect(bleTransport.getProtocolType(device.id)).toBeUndefined();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test('treats a disconnect with no reason as a real device drop', async () => {
|
|
845
|
+
// An older host bridge does not send `reason`; defaulting to "device left"
|
|
846
|
+
// preserves the pre-existing behaviour rather than silently ignoring it.
|
|
847
|
+
const device = { id: 'legacy-host-pro2-id', name: 'OneKey Pro 2' };
|
|
848
|
+
const nobleBle = createNobleBle(device);
|
|
849
|
+
const emitter = new EventEmitter();
|
|
850
|
+
let disconnectHandler: ((d: { id: string; name: string }) => void) | undefined;
|
|
851
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
852
|
+
disconnectHandler = handler;
|
|
853
|
+
return jest.fn();
|
|
854
|
+
});
|
|
855
|
+
echoProtocolV2(nobleBle, device.id);
|
|
856
|
+
const transportDisconnect = jest.fn();
|
|
857
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
858
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
859
|
+
|
|
860
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
861
|
+
disconnectHandler?.(device);
|
|
862
|
+
|
|
863
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
864
|
+
id: device.id,
|
|
865
|
+
connectId: device.id,
|
|
866
|
+
name: device.name,
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
|
|
613
870
|
test('preserves the active Protocol V2 link when the same schema is configured again', async () => {
|
|
614
871
|
const device = { id: 'stable-schema-pro2-id', name: 'OneKey Pro 2' };
|
|
615
872
|
const nobleBle = createNobleBle(device);
|