@onekeyfe/hd-transport-web-device 1.2.3-alpha.2 → 1.2.3-alpha.4
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 +109 -5
- package/__tests__/webusb-protocol-cache.test.ts +33 -0
- package/dist/electron-ble-transport.d.ts +1 -1
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +26 -21
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +38 -20
- package/src/webusb.ts +8 -4
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)
|
|
@@ -205,12 +205,52 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
205
205
|
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
test('
|
|
208
|
+
test('cancels a pending native connect acquire has not registered yet', async () => {
|
|
209
209
|
const device = { id: 'pending-connect-id', name: 'OneKey Pro 2' };
|
|
210
210
|
const nobleBle = createNobleBle(device);
|
|
211
|
+
// Never calls back: the field case the Core acquire deadline has to break.
|
|
212
|
+
nobleBle.connect.mockImplementation(
|
|
213
|
+
() =>
|
|
214
|
+
new Promise<void>(() => {
|
|
215
|
+
// intentionally never settles
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
219
|
+
|
|
220
|
+
const pendingAcquire = bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
221
|
+
pendingAcquire.catch(() => undefined);
|
|
222
|
+
await new Promise(resolve => {
|
|
223
|
+
setTimeout(resolve, 10);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// What the deadline / user abort calls. connectedDevices is still empty
|
|
227
|
+
// here, so this only works because the acquire is tracked as in flight.
|
|
228
|
+
await bleTransport.disconnect(device.id);
|
|
229
|
+
|
|
230
|
+
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
231
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('leaves a kept-alive link up when a released device is disconnected', async () => {
|
|
235
|
+
const device = { id: 'kept-alive-id', name: 'OneKey Pro 2' };
|
|
236
|
+
const nobleBle = createNobleBle(device);
|
|
237
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
238
|
+
|
|
239
|
+
// No acquire in flight and nothing in connectedDevices: the link was
|
|
240
|
+
// released logically and belongs to the main-process keep-alive timer.
|
|
241
|
+
// The routine post-call cancel must not cost the next call a cold connect.
|
|
242
|
+
await bleTransport.disconnect(device.id);
|
|
243
|
+
|
|
244
|
+
expect(nobleBle.disconnect).not.toHaveBeenCalled();
|
|
245
|
+
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('forceNative disconnects a device the renderer no longer tracks', async () => {
|
|
249
|
+
const device = { id: 'presumed-dead-id', name: 'OneKey Pro 2' };
|
|
250
|
+
const nobleBle = createNobleBle(device);
|
|
211
251
|
const bleTransport = configureTransport(nobleBle) as any;
|
|
212
252
|
|
|
213
|
-
await bleTransport.releaseNative(device.id);
|
|
253
|
+
await bleTransport.releaseNative(device.id, { forceNative: true });
|
|
214
254
|
|
|
215
255
|
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
216
256
|
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
@@ -498,7 +538,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
498
538
|
await expect(result).resolves.toBe('rejected');
|
|
499
539
|
});
|
|
500
540
|
|
|
501
|
-
test('
|
|
541
|
+
test('disconnects when both protocol probes fail and reconnects for the next acquire', async () => {
|
|
502
542
|
const device = { id: 'dead-device-id', name: 'Unknown Device' };
|
|
503
543
|
const nobleBle = createNobleBle(device);
|
|
504
544
|
|
|
@@ -510,7 +550,25 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
510
550
|
await expect(transport.acquire({ uuid: device.id })).rejects.toThrow(
|
|
511
551
|
/Unable to detect BLE protocol/
|
|
512
552
|
);
|
|
553
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
554
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
513
555
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
556
|
+
|
|
557
|
+
echoProtocolV2(nobleBle, device.id);
|
|
558
|
+
await expect(
|
|
559
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
560
|
+
).resolves.toMatchObject({ uuid: device.id });
|
|
561
|
+
expect(nobleBle.connect).toHaveBeenCalledTimes(2);
|
|
562
|
+
expect(nobleBle.disconnect.mock.invocationCallOrder.at(-1)).toBeLessThan(
|
|
563
|
+
nobleBle.connect.mock.invocationCallOrder[1]
|
|
564
|
+
);
|
|
565
|
+
await expect(
|
|
566
|
+
transport.call(device.id, 'Ping', { message: 'after-reconnect' })
|
|
567
|
+
).resolves.toMatchObject({
|
|
568
|
+
type: 'Success',
|
|
569
|
+
message: { message: 'ok' },
|
|
570
|
+
});
|
|
571
|
+
await transport.release(device.id);
|
|
514
572
|
});
|
|
515
573
|
|
|
516
574
|
test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
|
|
@@ -673,7 +731,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
673
731
|
}
|
|
674
732
|
});
|
|
675
733
|
|
|
676
|
-
test('
|
|
734
|
+
test('keeps a probe miss retryable after a previously confirmed Protocol V2 connection', async () => {
|
|
677
735
|
const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
|
|
678
736
|
const nobleBle = createNobleBle(device);
|
|
679
737
|
const transport = configureTransport(nobleBle);
|
|
@@ -686,7 +744,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
686
744
|
await expect(
|
|
687
745
|
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
688
746
|
).rejects.toMatchObject({
|
|
689
|
-
errorCode: HardwareErrorCode.
|
|
747
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
690
748
|
});
|
|
691
749
|
|
|
692
750
|
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
@@ -694,6 +752,52 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
694
752
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
695
753
|
});
|
|
696
754
|
|
|
755
|
+
test.each([false, true])(
|
|
756
|
+
'preserves an expected Protocol V2 Ping timeout with a prior successful connection: %s',
|
|
757
|
+
async previouslyConnected => {
|
|
758
|
+
const device = { id: 'timeout-pro2-id', name: 'OneKey Pro 2' };
|
|
759
|
+
const nobleBle = createNobleBle(device);
|
|
760
|
+
const transport = configureTransport(nobleBle);
|
|
761
|
+
|
|
762
|
+
if (previouslyConnected) {
|
|
763
|
+
echoProtocolV2(nobleBle, device.id);
|
|
764
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
765
|
+
await transport.release(device.id);
|
|
766
|
+
}
|
|
767
|
+
// Keep the real probe and response timer, but drop its notification.
|
|
768
|
+
nobleBle.write.mockImplementation(() => Promise.resolve());
|
|
769
|
+
|
|
770
|
+
await expect(
|
|
771
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
772
|
+
).rejects.toMatchObject({
|
|
773
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
774
|
+
message: 'BLE response timeout after 5000ms for Ping',
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
778
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
779
|
+
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
test('preserves a native stale-bond error during an expected Protocol V2 probe', async () => {
|
|
784
|
+
const device = { id: 'stale-bond-probe-id', name: 'OneKey Pro 2' };
|
|
785
|
+
const nobleBle = createNobleBle(device);
|
|
786
|
+
nobleBle.write.mockRejectedValue({
|
|
787
|
+
name: 'HardwareError',
|
|
788
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
789
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
790
|
+
});
|
|
791
|
+
const transport = configureTransport(nobleBle);
|
|
792
|
+
|
|
793
|
+
await expect(
|
|
794
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
795
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
|
|
796
|
+
|
|
797
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
798
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
799
|
+
});
|
|
800
|
+
|
|
697
801
|
test('does not take a Protocol V2 hint from the BLE name', async () => {
|
|
698
802
|
const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
|
|
699
803
|
const nobleBle = createNobleBle(device);
|
|
@@ -31,6 +31,39 @@ function buildAcquirableTransport(path = 'pro-webusb') {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
describe('WebUsbTransport protocol probe cache', () => {
|
|
34
|
+
test.each([
|
|
35
|
+
['V1', false, 0],
|
|
36
|
+
['V1', true, 1],
|
|
37
|
+
['V2', false, 1],
|
|
38
|
+
[undefined, false, 1],
|
|
39
|
+
] as const)(
|
|
40
|
+
'reconnect protocol=%s first=%s resets USB %s times',
|
|
41
|
+
async (protocol, first, resets) => {
|
|
42
|
+
const webusb = new WebUsbTransport();
|
|
43
|
+
const path = 'connected-usb-device';
|
|
44
|
+
const device = {
|
|
45
|
+
opened: true,
|
|
46
|
+
configuration: { configurationValue: 1 },
|
|
47
|
+
configurations: [],
|
|
48
|
+
reset: jest.fn().mockResolvedValue(undefined),
|
|
49
|
+
selectConfiguration: jest.fn().mockResolvedValue(undefined),
|
|
50
|
+
claimInterface: jest.fn().mockResolvedValue(undefined),
|
|
51
|
+
clearHalt: jest.fn().mockResolvedValue(undefined),
|
|
52
|
+
};
|
|
53
|
+
jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
|
|
54
|
+
jest.spyOn(webusb, 'getConnectedDevices').mockResolvedValue([]);
|
|
55
|
+
if (protocol) {
|
|
56
|
+
const state = webusb as unknown as { deviceProtocol: Map<string, 'V1' | 'V2'> };
|
|
57
|
+
state.deviceProtocol.set(path, protocol);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await webusb.connectToDevice(path, first);
|
|
61
|
+
|
|
62
|
+
expect(device.reset).toHaveBeenCalledTimes(resets);
|
|
63
|
+
expect(device.claimInterface).toHaveBeenCalledWith(0);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
34
67
|
test('acquire skips the wire probe when the protocol is already cached', async () => {
|
|
35
68
|
const webusb = buildAcquirableTransport();
|
|
36
69
|
const path = 'pro-webusb';
|
|
@@ -25,9 +25,9 @@ export default class ElectronBleTransport {
|
|
|
25
25
|
Log?: any;
|
|
26
26
|
emitter?: EventEmitter;
|
|
27
27
|
private connectedDevices;
|
|
28
|
+
private acquiringDevices;
|
|
28
29
|
private deviceProtocol;
|
|
29
30
|
private deviceProtocolHints;
|
|
30
|
-
private confirmedProtocolV2;
|
|
31
31
|
private deviceMtus;
|
|
32
32
|
private devicePacketCapacities;
|
|
33
33
|
private v1Buffers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AA0BA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,iCAAiC,CAAC;AAC5F,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAiDF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,OAAO,CAAC,kBAAkB,CAAuB;IAEjD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;
|
|
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
|
@@ -216,10 +216,16 @@ declare class ElectronBleTransport {
|
|
|
216
216
|
Log?: any;
|
|
217
217
|
emitter?: EventEmitter;
|
|
218
218
|
private connectedDevices;
|
|
219
|
+
/**
|
|
220
|
+
* Devices whose acquire() is still in flight.
|
|
221
|
+
*
|
|
222
|
+
* A native connect that never calls back has not reached `connectedDevices`
|
|
223
|
+
* yet, so this is the only record that the renderer is still trying to own
|
|
224
|
+
* the link; `releaseNative()` needs it to cancel that pending connect.
|
|
225
|
+
*/
|
|
226
|
+
private acquiringDevices;
|
|
219
227
|
private deviceProtocol;
|
|
220
228
|
private deviceProtocolHints;
|
|
221
|
-
/** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
|
|
222
|
-
private confirmedProtocolV2;
|
|
223
229
|
private deviceMtus;
|
|
224
230
|
private devicePacketCapacities;
|
|
225
231
|
private v1Buffers;
|
package/dist/index.js
CHANGED
|
@@ -375,11 +375,13 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
375
375
|
if (!device.opened) {
|
|
376
376
|
yield device.open();
|
|
377
377
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
379
|
+
try {
|
|
380
|
+
yield device.reset();
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
384
|
+
}
|
|
383
385
|
}
|
|
384
386
|
yield this.getConnectedDevices();
|
|
385
387
|
device = yield this.findDevice(path);
|
|
@@ -848,9 +850,9 @@ class ElectronBleTransport {
|
|
|
848
850
|
this.runPromise = null;
|
|
849
851
|
this.runPromiseDeviceId = null;
|
|
850
852
|
this.connectedDevices = new Set();
|
|
853
|
+
this.acquiringDevices = new Set();
|
|
851
854
|
this.deviceProtocol = new Map();
|
|
852
855
|
this.deviceProtocolHints = new Map();
|
|
853
|
-
this.confirmedProtocolV2 = new Set();
|
|
854
856
|
this.deviceMtus = new Map();
|
|
855
857
|
this.devicePacketCapacities = new Map();
|
|
856
858
|
this.v1Buffers = new Map();
|
|
@@ -874,7 +876,7 @@ class ElectronBleTransport {
|
|
|
874
876
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
875
877
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
876
878
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
877
|
-
yield this.releaseNative(uuid);
|
|
879
|
+
yield this.releaseNative(uuid, { forceNative: true });
|
|
878
880
|
}
|
|
879
881
|
}),
|
|
880
882
|
});
|
|
@@ -1033,6 +1035,7 @@ class ElectronBleTransport {
|
|
|
1033
1035
|
this.runPromise = null;
|
|
1034
1036
|
this.runPromiseDeviceId = null;
|
|
1035
1037
|
}
|
|
1038
|
+
this.acquiringDevices.add(uuid);
|
|
1036
1039
|
try {
|
|
1037
1040
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
1038
1041
|
throw new Error('Noble BLE API not available');
|
|
@@ -1099,6 +1102,9 @@ class ElectronBleTransport {
|
|
|
1099
1102
|
this.cleanupDeviceState(uuid);
|
|
1100
1103
|
this.handleBluetoothError(error);
|
|
1101
1104
|
}
|
|
1105
|
+
finally {
|
|
1106
|
+
this.acquiringDevices.delete(uuid);
|
|
1107
|
+
}
|
|
1102
1108
|
});
|
|
1103
1109
|
}
|
|
1104
1110
|
release(id, _onclose, keepSession) {
|
|
@@ -1119,12 +1125,14 @@ class ElectronBleTransport {
|
|
|
1119
1125
|
return this.releaseNative(id);
|
|
1120
1126
|
});
|
|
1121
1127
|
}
|
|
1122
|
-
releaseNative(id) {
|
|
1128
|
+
releaseNative(id, options) {
|
|
1123
1129
|
var _a, _b;
|
|
1124
1130
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1131
|
+
const rendererOwnsLink = this.connectedDevices.has(id) || this.acquiringDevices.has(id);
|
|
1132
|
+
const shouldDisconnect = (options === null || options === void 0 ? void 0 : options.forceNative) === true || rendererOwnsLink;
|
|
1125
1133
|
try {
|
|
1126
1134
|
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1127
|
-
if (nobleBle) {
|
|
1135
|
+
if (nobleBle && shouldDisconnect) {
|
|
1128
1136
|
if (this.connectedDevices.has(id)) {
|
|
1129
1137
|
yield invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
|
|
1130
1138
|
var _a;
|
|
@@ -1165,9 +1173,8 @@ class ElectronBleTransport {
|
|
|
1165
1173
|
}
|
|
1166
1174
|
});
|
|
1167
1175
|
}
|
|
1168
|
-
createProtocolMismatchError(expected
|
|
1169
|
-
|
|
1170
|
-
return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1176
|
+
createProtocolMismatchError(expected) {
|
|
1177
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1171
1178
|
}
|
|
1172
1179
|
createProtocolDetectionError() {
|
|
1173
1180
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
@@ -1186,13 +1193,12 @@ class ElectronBleTransport {
|
|
|
1186
1193
|
return 'V1';
|
|
1187
1194
|
}
|
|
1188
1195
|
if (expectedProtocol === 'V2') {
|
|
1189
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
1196
|
+
if (yield this.probeProtocolV2(uuid, expectedProtocol)) {
|
|
1190
1197
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1191
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1192
1198
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1193
1199
|
return 'V2';
|
|
1194
1200
|
}
|
|
1195
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
1201
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1196
1202
|
}
|
|
1197
1203
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1198
1204
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1203,9 +1209,6 @@ class ElectronBleTransport {
|
|
|
1203
1209
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1204
1210
|
if (detected) {
|
|
1205
1211
|
this.deviceProtocol.set(uuid, protocol);
|
|
1206
|
-
if (protocol === 'V2') {
|
|
1207
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1208
|
-
}
|
|
1209
1212
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1210
1213
|
return protocol;
|
|
1211
1214
|
}
|
|
@@ -1268,7 +1271,7 @@ class ElectronBleTransport {
|
|
|
1268
1271
|
}
|
|
1269
1272
|
});
|
|
1270
1273
|
}
|
|
1271
|
-
probeProtocolV2(uuid) {
|
|
1274
|
+
probeProtocolV2(uuid, expectedProtocol) {
|
|
1272
1275
|
var _a;
|
|
1273
1276
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1274
1277
|
if (!this._messages || !this._messagesV2) {
|
|
@@ -1286,7 +1289,9 @@ class ElectronBleTransport {
|
|
|
1286
1289
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1287
1290
|
this.resetProtocolV2Frames(uuid);
|
|
1288
1291
|
},
|
|
1289
|
-
shouldRethrow: error =>
|
|
1292
|
+
shouldRethrow: error => expectedProtocol === 'V2' ||
|
|
1293
|
+
hdShared.isBleStaleBondHardwareError(error) ||
|
|
1294
|
+
transport.isProtocolV2LinkDisabledError(error),
|
|
1290
1295
|
});
|
|
1291
1296
|
if (!detected) {
|
|
1292
1297
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1566,7 +1571,7 @@ class ElectronBleTransport {
|
|
|
1566
1571
|
this.notificationCleanups.delete(uuid);
|
|
1567
1572
|
this.notificationTokens.delete(uuid);
|
|
1568
1573
|
if (!isProbeTimeout) {
|
|
1569
|
-
yield this.releaseNative(uuid);
|
|
1574
|
+
yield this.releaseNative(uuid, { forceNative: true });
|
|
1570
1575
|
}
|
|
1571
1576
|
}
|
|
1572
1577
|
throw this.normalizeBluetoothError(e);
|
package/dist/webusb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAuBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAuCD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC7E,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;IAG9D,OAAO,CAAC,wBAAwB,CAAwC;IAExE,OAAO,CAAC,mBAAmB,CAAwC;IAMnE,OAAO,CAAC,kBAAkB,CAA0B;IAGpD,OAAO,CAAC,aAAa,CAA0B;IAS/C,OAAO,CAAC,mBAAmB,CAAqC;IAGhE,OAAO,CAAC,eAAe,CAA2C;IAElE,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAMvB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;;IAa3B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAmBxC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS;IAkBrD,iBAAiB,CAAC,IAAI,EAAE,MAAM;IAQ9B,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAsB7B,kBAAkB;IAmBlB,SAAS;IAQT,mBAAmB;IAmCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA+FjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAItB,cAAc;IAmEtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAuBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAuCD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC7E,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;IAG9D,OAAO,CAAC,wBAAwB,CAAwC;IAExE,OAAO,CAAC,mBAAmB,CAAwC;IAMnE,OAAO,CAAC,kBAAkB,CAA0B;IAGpD,OAAO,CAAC,aAAa,CAA0B;IAS/C,OAAO,CAAC,mBAAmB,CAAqC;IAGhE,OAAO,CAAC,eAAe,CAA2C;IAElE,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAMvB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;;IAa3B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAmBxC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS;IAkBrD,iBAAiB,CAAC,IAAI,EAAE,MAAM;IAQ9B,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAsB7B,kBAAkB;IAmBlB,SAAS;IAQT,mBAAmB;IAmCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA+FjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAItB,cAAc;IAmEtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;YAoCpC,eAAe;YAkBf,iBAAiB;IAsB/B,OAAO,CAAC,cAAc;IAMhB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IASvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAsCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,cAAc;YAWd,yBAAyB;YAKzB,yBAAyB;YAMzB,uBAAuB;YA0CvB,eAAe;YAoBf,eAAe;IAgBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAkCd,cAAc;IAYtB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAY1B,SAAS,CAAC,uBAAuB,IAAI,iBAAiB;IAUtD,SAAS,CAAC,sBAAsB;cAIhB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;cAIA,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC;cASN,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,SAAS,CAAC,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKrE,SAAS,CAAC,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK;IAWjF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.3-alpha.
|
|
3
|
+
"version": "1.2.3-alpha.4",
|
|
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.3-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.3-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.3-alpha.4",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.3-alpha.4"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.3-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.3-alpha.4",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "705729baaec54b86a4fc28cce310a85b67b82730"
|
|
33
33
|
}
|
|
@@ -117,13 +117,19 @@ export default class ElectronBleTransport {
|
|
|
117
117
|
|
|
118
118
|
private connectedDevices: Set<string> = new Set();
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Devices whose acquire() is still in flight.
|
|
122
|
+
*
|
|
123
|
+
* A native connect that never calls back has not reached `connectedDevices`
|
|
124
|
+
* yet, so this is the only record that the renderer is still trying to own
|
|
125
|
+
* the link; `releaseNative()` needs it to cancel that pending connect.
|
|
126
|
+
*/
|
|
127
|
+
private acquiringDevices: Set<string> = new Set();
|
|
128
|
+
|
|
120
129
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
121
130
|
|
|
122
131
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
123
132
|
|
|
124
|
-
/** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
|
|
125
|
-
private confirmedProtocolV2 = new Set<string>();
|
|
126
|
-
|
|
127
133
|
private deviceMtus: Map<string, number> = new Map();
|
|
128
134
|
|
|
129
135
|
private devicePacketCapacities: Map<string, number> = new Map();
|
|
@@ -152,7 +158,7 @@ export default class ElectronBleTransport {
|
|
|
152
158
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
153
159
|
this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
154
160
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
155
|
-
await this.releaseNative(uuid);
|
|
161
|
+
await this.releaseNative(uuid, { forceNative: true });
|
|
156
162
|
}
|
|
157
163
|
},
|
|
158
164
|
});
|
|
@@ -366,6 +372,7 @@ export default class ElectronBleTransport {
|
|
|
366
372
|
this.runPromiseDeviceId = null;
|
|
367
373
|
}
|
|
368
374
|
|
|
375
|
+
this.acquiringDevices.add(uuid);
|
|
369
376
|
try {
|
|
370
377
|
if (!window.desktopApi?.nobleBle) {
|
|
371
378
|
throw new Error('Noble BLE API not available');
|
|
@@ -437,6 +444,8 @@ export default class ElectronBleTransport {
|
|
|
437
444
|
}
|
|
438
445
|
this.cleanupDeviceState(uuid);
|
|
439
446
|
this.handleBluetoothError(error);
|
|
447
|
+
} finally {
|
|
448
|
+
this.acquiringDevices.delete(uuid);
|
|
440
449
|
}
|
|
441
450
|
}
|
|
442
451
|
|
|
@@ -457,10 +466,23 @@ export default class ElectronBleTransport {
|
|
|
457
466
|
}
|
|
458
467
|
|
|
459
468
|
// Hard teardown, error paths only: a link presumed dead must not be reused.
|
|
460
|
-
|
|
469
|
+
//
|
|
470
|
+
// The native disconnect is sent while the renderer still owns the link, or is
|
|
471
|
+
// still trying to get it: a stuck acquire has not reached `connectedDevices`
|
|
472
|
+
// yet, and its pending native connect can only be cancelled from here, so the
|
|
473
|
+
// Core acquire deadline would otherwise be unable to terminate it.
|
|
474
|
+
//
|
|
475
|
+
// It is NOT sent for a device the renderer has already released logically.
|
|
476
|
+
// That link belongs to the main-process keep-alive timer, and dropping it on
|
|
477
|
+
// the routine post-call `cancel()` (Device.interruptionFromUser, no acquire
|
|
478
|
+
// held, empty request queue) costs a full cold reconnect on the very next
|
|
479
|
+
// operation — measured 2.93s on Pro and 17-26s on Pro 2.
|
|
480
|
+
private async releaseNative(id: string, options?: { forceNative?: boolean }) {
|
|
481
|
+
const rendererOwnsLink = this.connectedDevices.has(id) || this.acquiringDevices.has(id);
|
|
482
|
+
const shouldDisconnect = options?.forceNative === true || rendererOwnsLink;
|
|
461
483
|
try {
|
|
462
484
|
const nobleBle = window.desktopApi?.nobleBle;
|
|
463
|
-
if (nobleBle) {
|
|
485
|
+
if (nobleBle && shouldDisconnect) {
|
|
464
486
|
if (this.connectedDevices.has(id)) {
|
|
465
487
|
await invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
|
|
466
488
|
this.Log?.debug('[Electron BLE] release unsubscribe failed:', error);
|
|
@@ -501,12 +523,9 @@ export default class ElectronBleTransport {
|
|
|
501
523
|
}
|
|
502
524
|
}
|
|
503
525
|
|
|
504
|
-
private createProtocolMismatchError(expected: ProtocolType
|
|
505
|
-
// A generic Ping miss is not a bond failure. Only a later miss after this
|
|
506
|
-
// endpoint already answered V2, or a native encryption/pairing error, is.
|
|
507
|
-
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
526
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
508
527
|
return ERRORS.TypedError(
|
|
509
|
-
|
|
528
|
+
HardwareErrorCode.RuntimeError,
|
|
510
529
|
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
511
530
|
);
|
|
512
531
|
}
|
|
@@ -546,13 +565,12 @@ export default class ElectronBleTransport {
|
|
|
546
565
|
}
|
|
547
566
|
|
|
548
567
|
if (expectedProtocol === 'V2') {
|
|
549
|
-
if (await this.probeProtocolV2(uuid)) {
|
|
568
|
+
if (await this.probeProtocolV2(uuid, expectedProtocol)) {
|
|
550
569
|
this.deviceProtocol.set(uuid, 'V2');
|
|
551
|
-
this.confirmedProtocolV2.add(uuid);
|
|
552
570
|
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
553
571
|
return 'V2';
|
|
554
572
|
}
|
|
555
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
573
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
556
574
|
}
|
|
557
575
|
|
|
558
576
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
@@ -571,9 +589,6 @@ export default class ElectronBleTransport {
|
|
|
571
589
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
572
590
|
if (detected) {
|
|
573
591
|
this.deviceProtocol.set(uuid, protocol);
|
|
574
|
-
if (protocol === 'V2') {
|
|
575
|
-
this.confirmedProtocolV2.add(uuid);
|
|
576
|
-
}
|
|
577
592
|
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
578
593
|
return protocol;
|
|
579
594
|
}
|
|
@@ -644,7 +659,7 @@ export default class ElectronBleTransport {
|
|
|
644
659
|
}
|
|
645
660
|
}
|
|
646
661
|
|
|
647
|
-
private async probeProtocolV2(uuid: string) {
|
|
662
|
+
private async probeProtocolV2(uuid: string, expectedProtocol?: ProtocolType) {
|
|
648
663
|
if (!this._messages || !this._messagesV2) {
|
|
649
664
|
return false;
|
|
650
665
|
}
|
|
@@ -660,8 +675,11 @@ export default class ElectronBleTransport {
|
|
|
660
675
|
this.v2Assemblers.get(uuid)?.reset();
|
|
661
676
|
this.resetProtocolV2Frames(uuid);
|
|
662
677
|
},
|
|
678
|
+
// A declared V2 protocol needs no fallback; preserve the actual link failure.
|
|
663
679
|
shouldRethrow: error =>
|
|
664
|
-
|
|
680
|
+
expectedProtocol === 'V2' ||
|
|
681
|
+
isBleStaleBondHardwareError(error) ||
|
|
682
|
+
isProtocolV2LinkDisabledError(error),
|
|
665
683
|
});
|
|
666
684
|
if (!detected) {
|
|
667
685
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -977,7 +995,7 @@ export default class ElectronBleTransport {
|
|
|
977
995
|
this.notificationCleanups.delete(uuid);
|
|
978
996
|
this.notificationTokens.delete(uuid);
|
|
979
997
|
if (!isProbeTimeout) {
|
|
980
|
-
await this.releaseNative(uuid);
|
|
998
|
+
await this.releaseNative(uuid, { forceNative: true });
|
|
981
999
|
}
|
|
982
1000
|
}
|
|
983
1001
|
throw this.normalizeBluetoothError(e);
|
package/src/webusb.ts
CHANGED
|
@@ -566,10 +566,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
566
566
|
if (!device.opened) {
|
|
567
567
|
await device.open();
|
|
568
568
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
569
|
+
// A V1 packet retry continues an in-flight exchange. Preserve its endpoint
|
|
570
|
+
// state as in the legacy transport; probing and V2 recovery still reset.
|
|
571
|
+
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
572
|
+
try {
|
|
573
|
+
await device.reset();
|
|
574
|
+
} catch (error) {
|
|
575
|
+
this.Log?.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
576
|
+
}
|
|
573
577
|
}
|
|
574
578
|
await this.getConnectedDevices();
|
|
575
579
|
device = await this.findDevice(path);
|