@onekeyfe/hd-transport-web-device 1.2.3-alpha.3 → 1.2.3-alpha.5
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 +81 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +9 -3
- package/dist/electron-ble-transport.d.ts +1 -1
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.js +102 -28
- package/dist/webusb.d.ts +6 -2
- 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 +89 -11
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,87 @@ 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
|
+
|
|
67
|
+
test('reports claimInterface failures as WebUSB device access errors', async () => {
|
|
68
|
+
const webusb = new WebUsbTransport();
|
|
69
|
+
const path = 'connected-usb-device';
|
|
70
|
+
const claimError = Object.assign(new Error('Unable to claim interface'), {
|
|
71
|
+
name: 'NetworkError',
|
|
72
|
+
});
|
|
73
|
+
const device = {
|
|
74
|
+
opened: true,
|
|
75
|
+
configuration: { configurationValue: 1 },
|
|
76
|
+
configurations: [],
|
|
77
|
+
claimInterface: jest.fn().mockRejectedValue(claimError),
|
|
78
|
+
};
|
|
79
|
+
jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
|
|
80
|
+
jest.spyOn(webusb, 'getConnectedDevices').mockResolvedValue([]);
|
|
81
|
+
const state = webusb as unknown as { deviceProtocol: Map<string, 'V1' | 'V2'> };
|
|
82
|
+
state.deviceProtocol.set(path, 'V1');
|
|
83
|
+
|
|
84
|
+
await expect(webusb.connectToDevice(path, false)).rejects.toMatchObject({
|
|
85
|
+
errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
|
|
86
|
+
params: {
|
|
87
|
+
operation: 'claimInterface',
|
|
88
|
+
nativeErrorName: 'NetworkError',
|
|
89
|
+
nativeErrorMessage: 'Unable to claim interface',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('reports transferIn failures as WebUSB device access errors', async () => {
|
|
95
|
+
const webusb = new WebUsbTransport() as any;
|
|
96
|
+
const path = 'connected-usb-device';
|
|
97
|
+
const transferError = Object.assign(new Error('Transfer endpoint is unavailable'), {
|
|
98
|
+
name: 'NetworkError',
|
|
99
|
+
});
|
|
100
|
+
webusb.findDevice = jest.fn().mockResolvedValue({
|
|
101
|
+
opened: true,
|
|
102
|
+
transferIn: jest.fn().mockRejectedValue(transferError),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await expect(webusb.transferInWithRetry(path, 64)).rejects.toMatchObject({
|
|
106
|
+
errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
|
|
107
|
+
params: {
|
|
108
|
+
operation: 'transferIn',
|
|
109
|
+
nativeErrorName: 'NetworkError',
|
|
110
|
+
nativeErrorMessage: 'Transfer endpoint is unavailable',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
34
115
|
test('acquire skips the wire probe when the protocol is already cached', async () => {
|
|
35
116
|
const webusb = buildAcquirableTransport();
|
|
36
117
|
const path = 'pro-webusb';
|
|
@@ -238,9 +238,15 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
238
238
|
webusb.resetConnectionAfterProbe = jest.fn();
|
|
239
239
|
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
240
240
|
|
|
241
|
-
await expect(
|
|
242
|
-
'
|
|
243
|
-
)
|
|
241
|
+
await expect(
|
|
242
|
+
webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })
|
|
243
|
+
).rejects.toMatchObject({
|
|
244
|
+
errorCode: HardwareErrorCode.BridgeDeviceDisconnected,
|
|
245
|
+
params: {
|
|
246
|
+
operation: 'transferIn',
|
|
247
|
+
nativeErrorMessage: 'NetworkError: transferIn device disconnected',
|
|
248
|
+
},
|
|
249
|
+
});
|
|
244
250
|
|
|
245
251
|
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
246
252
|
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
@@ -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
|
@@ -118,7 +118,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
118
118
|
/**
|
|
119
119
|
* Connect to device with retry mechanism
|
|
120
120
|
*/
|
|
121
|
-
connect(path: string, first: boolean): Promise<
|
|
121
|
+
connect(path: string, first: boolean): Promise<undefined>;
|
|
122
122
|
/**
|
|
123
123
|
* Discover vendor-class (0xFF) interface and its IN/OUT endpoint numbers from USB descriptors.
|
|
124
124
|
* Falls back to legacy hardcoded values if no vendor interface is found.
|
|
@@ -128,7 +128,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
128
128
|
* Connect to specific device.
|
|
129
129
|
* Discovers interface/endpoint numbers from USB descriptors on first connection.
|
|
130
130
|
*/
|
|
131
|
-
connectToDevice(path: string, first: boolean): Promise<
|
|
131
|
+
connectToDevice(path: string, first: boolean): Promise<undefined>;
|
|
132
132
|
private closeOpenDevice;
|
|
133
133
|
private clearEndpointHalt;
|
|
134
134
|
/**
|
|
@@ -141,6 +141,10 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
141
141
|
private assertAcquired;
|
|
142
142
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
143
143
|
private getErrorMessage;
|
|
144
|
+
private getErrorName;
|
|
145
|
+
private isWebUsbIoHardwareError;
|
|
146
|
+
private isDeviceStillEnumerated;
|
|
147
|
+
private throwWebUsbIoError;
|
|
144
148
|
private isRetryablePacketIoError;
|
|
145
149
|
private reconnectForPacketIoRetry;
|
|
146
150
|
private getTransferInData;
|
|
@@ -216,10 +220,16 @@ declare class ElectronBleTransport {
|
|
|
216
220
|
Log?: any;
|
|
217
221
|
emitter?: EventEmitter;
|
|
218
222
|
private connectedDevices;
|
|
223
|
+
/**
|
|
224
|
+
* Devices whose acquire() is still in flight.
|
|
225
|
+
*
|
|
226
|
+
* A native connect that never calls back has not reached `connectedDevices`
|
|
227
|
+
* yet, so this is the only record that the renderer is still trying to own
|
|
228
|
+
* the link; `releaseNative()` needs it to cancel that pending connect.
|
|
229
|
+
*/
|
|
230
|
+
private acquiringDevices;
|
|
219
231
|
private deviceProtocol;
|
|
220
232
|
private deviceProtocolHints;
|
|
221
|
-
/** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
|
|
222
|
-
private confirmedProtocolV2;
|
|
223
233
|
private deviceMtus;
|
|
224
234
|
private devicePacketCapacities;
|
|
225
235
|
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);
|
|
@@ -393,7 +395,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
393
395
|
}
|
|
394
396
|
const endpoints = this.discoverEndpoints(device);
|
|
395
397
|
this.deviceEndpoints.set(path, endpoints);
|
|
396
|
-
|
|
398
|
+
try {
|
|
399
|
+
yield device.claimInterface(endpoints.interfaceNumber);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
403
|
+
}
|
|
397
404
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
398
405
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
399
406
|
});
|
|
@@ -457,6 +464,59 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
457
464
|
}
|
|
458
465
|
return String(error);
|
|
459
466
|
}
|
|
467
|
+
getErrorName(error) {
|
|
468
|
+
if (typeof error === 'object' && error && 'name' in error) {
|
|
469
|
+
const { name } = error;
|
|
470
|
+
return typeof name === 'string' ? name : String(name !== null && name !== void 0 ? name : '');
|
|
471
|
+
}
|
|
472
|
+
return '';
|
|
473
|
+
}
|
|
474
|
+
isWebUsbIoHardwareError(error) {
|
|
475
|
+
return (error instanceof hdShared.HardwareError &&
|
|
476
|
+
(error.errorCode === hdShared.HardwareErrorCode.BridgeDeviceDisconnected ||
|
|
477
|
+
error.errorCode === hdShared.HardwareErrorCode.WebUsbDeviceAccessError));
|
|
478
|
+
}
|
|
479
|
+
isDeviceStillEnumerated(path) {
|
|
480
|
+
var _a;
|
|
481
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
482
|
+
if (!this.usb)
|
|
483
|
+
return undefined;
|
|
484
|
+
try {
|
|
485
|
+
const devices = yield this.usb.getDevices();
|
|
486
|
+
return devices.some(device => hdShared.resolveOneKeyUsbDevicePath(device) === path);
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] failed to verify WebUSB device presence:', error);
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
throwWebUsbIoError(path, operation, error) {
|
|
495
|
+
var _a;
|
|
496
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
497
|
+
if (this.isWebUsbIoHardwareError(error))
|
|
498
|
+
throw error;
|
|
499
|
+
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
500
|
+
throw error;
|
|
501
|
+
const nativeError = error instanceof transport.ProtocolV2LinkError ? (_a = error.cause) !== null && _a !== void 0 ? _a : error : error;
|
|
502
|
+
if (this.isWebUsbIoHardwareError(nativeError))
|
|
503
|
+
throw nativeError;
|
|
504
|
+
const nativeErrorName = this.getErrorName(nativeError);
|
|
505
|
+
const nativeErrorMessage = this.getErrorMessage(nativeError);
|
|
506
|
+
const disconnectedByMessage = /(?:device )?disconnected|device not found|action was interrupted/i.test(`${nativeErrorName}: ${nativeErrorMessage}`);
|
|
507
|
+
const stillEnumerated = disconnectedByMessage
|
|
508
|
+
? false
|
|
509
|
+
: yield this.isDeviceStillEnumerated(path);
|
|
510
|
+
const errorCode = disconnectedByMessage || stillEnumerated === false
|
|
511
|
+
? hdShared.HardwareErrorCode.BridgeDeviceDisconnected
|
|
512
|
+
: hdShared.HardwareErrorCode.WebUsbDeviceAccessError;
|
|
513
|
+
throw hdShared.ERRORS.TypedError(errorCode, nativeErrorMessage || undefined, {
|
|
514
|
+
operation,
|
|
515
|
+
nativeErrorName: nativeErrorName || undefined,
|
|
516
|
+
nativeErrorMessage: nativeErrorMessage || undefined,
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
}
|
|
460
520
|
isRetryablePacketIoError(error) {
|
|
461
521
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
462
522
|
return (message.includes('transferout') ||
|
|
@@ -523,7 +583,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
523
583
|
lastError = error;
|
|
524
584
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
525
585
|
if (!shouldRetry) {
|
|
526
|
-
|
|
586
|
+
return this.throwWebUsbIoError(path, 'transferOut', error);
|
|
527
587
|
}
|
|
528
588
|
try {
|
|
529
589
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
@@ -534,7 +594,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
534
594
|
}
|
|
535
595
|
}
|
|
536
596
|
}
|
|
537
|
-
|
|
597
|
+
return this.throwWebUsbIoError(path, 'transferOut', lastError);
|
|
538
598
|
});
|
|
539
599
|
}
|
|
540
600
|
transferOutOnce(path, packet) {
|
|
@@ -575,7 +635,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
575
635
|
}
|
|
576
636
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
577
637
|
if (!shouldRetry) {
|
|
578
|
-
|
|
638
|
+
return this.throwWebUsbIoError(path, 'transferIn', error);
|
|
579
639
|
}
|
|
580
640
|
try {
|
|
581
641
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
@@ -586,7 +646,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
586
646
|
}
|
|
587
647
|
}
|
|
588
648
|
}
|
|
589
|
-
|
|
649
|
+
return this.throwWebUsbIoError(path, 'transferIn', lastError);
|
|
590
650
|
});
|
|
591
651
|
}
|
|
592
652
|
transferInOnce(path, length) {
|
|
@@ -663,7 +723,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
663
723
|
});
|
|
664
724
|
return true;
|
|
665
725
|
}
|
|
666
|
-
catch (
|
|
726
|
+
catch (error) {
|
|
727
|
+
if (this.isWebUsbIoHardwareError(error))
|
|
728
|
+
throw error;
|
|
667
729
|
return false;
|
|
668
730
|
}
|
|
669
731
|
});
|
|
@@ -678,6 +740,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
678
740
|
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
679
741
|
logger: this.Log,
|
|
680
742
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
743
|
+
shouldRethrow: error => this.isWebUsbIoHardwareError(error),
|
|
681
744
|
});
|
|
682
745
|
});
|
|
683
746
|
}
|
|
@@ -724,7 +787,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
724
787
|
}
|
|
725
788
|
callProtocolV2(path, name, data, options) {
|
|
726
789
|
return __awaiter(this, void 0, void 0, function* () {
|
|
727
|
-
|
|
790
|
+
try {
|
|
791
|
+
return yield this.callProtocolV2Usb(path, name, data, options);
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
if (!(error instanceof transport.ProtocolV2LinkError) || error.code !== 'io')
|
|
795
|
+
throw error;
|
|
796
|
+
const operation = error.message.includes(' USB write failed:') ? 'transferOut' : 'transferIn';
|
|
797
|
+
return this.throwWebUsbIoError(path, operation, error);
|
|
798
|
+
}
|
|
728
799
|
});
|
|
729
800
|
}
|
|
730
801
|
receiveData(path, timeoutMs) {
|
|
@@ -848,9 +919,9 @@ class ElectronBleTransport {
|
|
|
848
919
|
this.runPromise = null;
|
|
849
920
|
this.runPromiseDeviceId = null;
|
|
850
921
|
this.connectedDevices = new Set();
|
|
922
|
+
this.acquiringDevices = new Set();
|
|
851
923
|
this.deviceProtocol = new Map();
|
|
852
924
|
this.deviceProtocolHints = new Map();
|
|
853
|
-
this.confirmedProtocolV2 = new Set();
|
|
854
925
|
this.deviceMtus = new Map();
|
|
855
926
|
this.devicePacketCapacities = new Map();
|
|
856
927
|
this.v1Buffers = new Map();
|
|
@@ -874,7 +945,7 @@ class ElectronBleTransport {
|
|
|
874
945
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
875
946
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
876
947
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
877
|
-
yield this.releaseNative(uuid);
|
|
948
|
+
yield this.releaseNative(uuid, { forceNative: true });
|
|
878
949
|
}
|
|
879
950
|
}),
|
|
880
951
|
});
|
|
@@ -1033,6 +1104,7 @@ class ElectronBleTransport {
|
|
|
1033
1104
|
this.runPromise = null;
|
|
1034
1105
|
this.runPromiseDeviceId = null;
|
|
1035
1106
|
}
|
|
1107
|
+
this.acquiringDevices.add(uuid);
|
|
1036
1108
|
try {
|
|
1037
1109
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
1038
1110
|
throw new Error('Noble BLE API not available');
|
|
@@ -1099,6 +1171,9 @@ class ElectronBleTransport {
|
|
|
1099
1171
|
this.cleanupDeviceState(uuid);
|
|
1100
1172
|
this.handleBluetoothError(error);
|
|
1101
1173
|
}
|
|
1174
|
+
finally {
|
|
1175
|
+
this.acquiringDevices.delete(uuid);
|
|
1176
|
+
}
|
|
1102
1177
|
});
|
|
1103
1178
|
}
|
|
1104
1179
|
release(id, _onclose, keepSession) {
|
|
@@ -1119,12 +1194,14 @@ class ElectronBleTransport {
|
|
|
1119
1194
|
return this.releaseNative(id);
|
|
1120
1195
|
});
|
|
1121
1196
|
}
|
|
1122
|
-
releaseNative(id) {
|
|
1197
|
+
releaseNative(id, options) {
|
|
1123
1198
|
var _a, _b;
|
|
1124
1199
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1200
|
+
const rendererOwnsLink = this.connectedDevices.has(id) || this.acquiringDevices.has(id);
|
|
1201
|
+
const shouldDisconnect = (options === null || options === void 0 ? void 0 : options.forceNative) === true || rendererOwnsLink;
|
|
1125
1202
|
try {
|
|
1126
1203
|
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1127
|
-
if (nobleBle) {
|
|
1204
|
+
if (nobleBle && shouldDisconnect) {
|
|
1128
1205
|
if (this.connectedDevices.has(id)) {
|
|
1129
1206
|
yield invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
|
|
1130
1207
|
var _a;
|
|
@@ -1165,9 +1242,8 @@ class ElectronBleTransport {
|
|
|
1165
1242
|
}
|
|
1166
1243
|
});
|
|
1167
1244
|
}
|
|
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`);
|
|
1245
|
+
createProtocolMismatchError(expected) {
|
|
1246
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1171
1247
|
}
|
|
1172
1248
|
createProtocolDetectionError() {
|
|
1173
1249
|
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 +1262,12 @@ class ElectronBleTransport {
|
|
|
1186
1262
|
return 'V1';
|
|
1187
1263
|
}
|
|
1188
1264
|
if (expectedProtocol === 'V2') {
|
|
1189
|
-
if (yield this.probeProtocolV2(uuid)) {
|
|
1265
|
+
if (yield this.probeProtocolV2(uuid, expectedProtocol)) {
|
|
1190
1266
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1191
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1192
1267
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1193
1268
|
return 'V2';
|
|
1194
1269
|
}
|
|
1195
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
1270
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1196
1271
|
}
|
|
1197
1272
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1198
1273
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1203,9 +1278,6 @@ class ElectronBleTransport {
|
|
|
1203
1278
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1204
1279
|
if (detected) {
|
|
1205
1280
|
this.deviceProtocol.set(uuid, protocol);
|
|
1206
|
-
if (protocol === 'V2') {
|
|
1207
|
-
this.confirmedProtocolV2.add(uuid);
|
|
1208
|
-
}
|
|
1209
1281
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1210
1282
|
return protocol;
|
|
1211
1283
|
}
|
|
@@ -1268,7 +1340,7 @@ class ElectronBleTransport {
|
|
|
1268
1340
|
}
|
|
1269
1341
|
});
|
|
1270
1342
|
}
|
|
1271
|
-
probeProtocolV2(uuid) {
|
|
1343
|
+
probeProtocolV2(uuid, expectedProtocol) {
|
|
1272
1344
|
var _a;
|
|
1273
1345
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1274
1346
|
if (!this._messages || !this._messagesV2) {
|
|
@@ -1286,7 +1358,9 @@ class ElectronBleTransport {
|
|
|
1286
1358
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1287
1359
|
this.resetProtocolV2Frames(uuid);
|
|
1288
1360
|
},
|
|
1289
|
-
shouldRethrow: error =>
|
|
1361
|
+
shouldRethrow: error => expectedProtocol === 'V2' ||
|
|
1362
|
+
hdShared.isBleStaleBondHardwareError(error) ||
|
|
1363
|
+
transport.isProtocolV2LinkDisabledError(error),
|
|
1290
1364
|
});
|
|
1291
1365
|
if (!detected) {
|
|
1292
1366
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1566,7 +1640,7 @@ class ElectronBleTransport {
|
|
|
1566
1640
|
this.notificationCleanups.delete(uuid);
|
|
1567
1641
|
this.notificationTokens.delete(uuid);
|
|
1568
1642
|
if (!isProbeTimeout) {
|
|
1569
|
-
yield this.releaseNative(uuid);
|
|
1643
|
+
yield this.releaseNative(uuid, { forceNative: true });
|
|
1570
1644
|
}
|
|
1571
1645
|
}
|
|
1572
1646
|
throw this.normalizeBluetoothError(e);
|
package/dist/webusb.d.ts
CHANGED
|
@@ -44,14 +44,18 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
44
44
|
private createProtocolDetectionError;
|
|
45
45
|
private detectProtocol;
|
|
46
46
|
findDevice(path: string): Promise<USBDevice>;
|
|
47
|
-
connect(path: string, first: boolean): Promise<
|
|
47
|
+
connect(path: string, first: boolean): Promise<undefined>;
|
|
48
48
|
private discoverEndpoints;
|
|
49
|
-
connectToDevice(path: string, first: boolean): Promise<
|
|
49
|
+
connectToDevice(path: string, first: boolean): Promise<undefined>;
|
|
50
50
|
private closeOpenDevice;
|
|
51
51
|
private clearEndpointHalt;
|
|
52
52
|
private assertAcquired;
|
|
53
53
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
54
54
|
private getErrorMessage;
|
|
55
|
+
private getErrorName;
|
|
56
|
+
private isWebUsbIoHardwareError;
|
|
57
|
+
private isDeviceStillEnumerated;
|
|
58
|
+
private throwWebUsbIoError;
|
|
55
59
|
private isRetryablePacketIoError;
|
|
56
60
|
private reconnectForPacketIoRetry;
|
|
57
61
|
private getTransferInData;
|
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;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
|
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.5",
|
|
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.5",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.3-alpha.5"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.3-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.3-alpha.5",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "ee493c3ad41a6bc7fb249fba71d4b9bf8efcced8"
|
|
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
|
@@ -13,6 +13,7 @@ import transport, {
|
|
|
13
13
|
} from '@onekeyfe/hd-transport';
|
|
14
14
|
import {
|
|
15
15
|
ERRORS,
|
|
16
|
+
HardwareError,
|
|
16
17
|
HardwareErrorCode,
|
|
17
18
|
ONEKEY_WEBUSB_FILTER,
|
|
18
19
|
inferProtocolHintFromUsbId,
|
|
@@ -70,6 +71,8 @@ interface TransferCancelToken {
|
|
|
70
71
|
cancelled: boolean;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
type WebUsbOperation = 'claimInterface' | 'transferIn' | 'transferOut';
|
|
75
|
+
|
|
73
76
|
/**
|
|
74
77
|
* The navigator.usb disconnect listener is module-scoped and attached at most
|
|
75
78
|
* once: listeners on navigator.usb are global and never garbage collected, so a
|
|
@@ -566,10 +569,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
566
569
|
if (!device.opened) {
|
|
567
570
|
await device.open();
|
|
568
571
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
572
|
+
// A V1 packet retry continues an in-flight exchange. Preserve its endpoint
|
|
573
|
+
// state as in the legacy transport; probing and V2 recovery still reset.
|
|
574
|
+
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
575
|
+
try {
|
|
576
|
+
await device.reset();
|
|
577
|
+
} catch (error) {
|
|
578
|
+
this.Log?.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
579
|
+
}
|
|
573
580
|
}
|
|
574
581
|
await this.getConnectedDevices();
|
|
575
582
|
device = await this.findDevice(path);
|
|
@@ -588,7 +595,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
588
595
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
589
596
|
const endpoints = this.discoverEndpoints(device);
|
|
590
597
|
this.deviceEndpoints.set(path, endpoints);
|
|
591
|
-
|
|
598
|
+
try {
|
|
599
|
+
await device.claimInterface(endpoints.interfaceNumber);
|
|
600
|
+
} catch (error) {
|
|
601
|
+
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
602
|
+
}
|
|
592
603
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
593
604
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
594
605
|
}
|
|
@@ -658,6 +669,65 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
658
669
|
return String(error);
|
|
659
670
|
}
|
|
660
671
|
|
|
672
|
+
private getErrorName(error: unknown) {
|
|
673
|
+
if (typeof error === 'object' && error && 'name' in error) {
|
|
674
|
+
const { name } = error as { name?: unknown };
|
|
675
|
+
return typeof name === 'string' ? name : String(name ?? '');
|
|
676
|
+
}
|
|
677
|
+
return '';
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private isWebUsbIoHardwareError(error: unknown): error is HardwareError {
|
|
681
|
+
return (
|
|
682
|
+
error instanceof HardwareError &&
|
|
683
|
+
(error.errorCode === HardwareErrorCode.BridgeDeviceDisconnected ||
|
|
684
|
+
error.errorCode === HardwareErrorCode.WebUsbDeviceAccessError)
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private async isDeviceStillEnumerated(path: string): Promise<boolean | undefined> {
|
|
689
|
+
if (!this.usb) return undefined;
|
|
690
|
+
try {
|
|
691
|
+
const devices = await this.usb.getDevices();
|
|
692
|
+
return devices.some(device => resolveOneKeyUsbDevicePath(device) === path);
|
|
693
|
+
} catch (error) {
|
|
694
|
+
this.Log?.debug('[WebUsbTransport] failed to verify WebUSB device presence:', error);
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
private async throwWebUsbIoError(
|
|
700
|
+
path: string,
|
|
701
|
+
operation: WebUsbOperation,
|
|
702
|
+
error: unknown
|
|
703
|
+
): Promise<never> {
|
|
704
|
+
if (this.isWebUsbIoHardwareError(error)) throw error;
|
|
705
|
+
if (error instanceof ProtocolV2LinkError && error.code !== 'io') throw error;
|
|
706
|
+
|
|
707
|
+
const nativeError = error instanceof ProtocolV2LinkError ? error.cause ?? error : error;
|
|
708
|
+
if (this.isWebUsbIoHardwareError(nativeError)) throw nativeError;
|
|
709
|
+
|
|
710
|
+
const nativeErrorName = this.getErrorName(nativeError);
|
|
711
|
+
const nativeErrorMessage = this.getErrorMessage(nativeError);
|
|
712
|
+
const disconnectedByMessage =
|
|
713
|
+
/(?:device )?disconnected|device not found|action was interrupted/i.test(
|
|
714
|
+
`${nativeErrorName}: ${nativeErrorMessage}`
|
|
715
|
+
);
|
|
716
|
+
const stillEnumerated = disconnectedByMessage
|
|
717
|
+
? false
|
|
718
|
+
: await this.isDeviceStillEnumerated(path);
|
|
719
|
+
const errorCode =
|
|
720
|
+
disconnectedByMessage || stillEnumerated === false
|
|
721
|
+
? HardwareErrorCode.BridgeDeviceDisconnected
|
|
722
|
+
: HardwareErrorCode.WebUsbDeviceAccessError;
|
|
723
|
+
|
|
724
|
+
throw ERRORS.TypedError(errorCode, nativeErrorMessage || undefined, {
|
|
725
|
+
operation,
|
|
726
|
+
nativeErrorName: nativeErrorName || undefined,
|
|
727
|
+
nativeErrorMessage: nativeErrorMessage || undefined,
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
661
731
|
private isRetryablePacketIoError(error: unknown) {
|
|
662
732
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
663
733
|
return (
|
|
@@ -738,7 +808,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
738
808
|
lastError = error;
|
|
739
809
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
740
810
|
if (!shouldRetry) {
|
|
741
|
-
|
|
811
|
+
return this.throwWebUsbIoError(path, 'transferOut', error);
|
|
742
812
|
}
|
|
743
813
|
try {
|
|
744
814
|
await this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
@@ -752,7 +822,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
752
822
|
}
|
|
753
823
|
}
|
|
754
824
|
}
|
|
755
|
-
|
|
825
|
+
return this.throwWebUsbIoError(path, 'transferOut', lastError);
|
|
756
826
|
}
|
|
757
827
|
|
|
758
828
|
private async transferOutOnce(path: string, packet: Uint8Array) {
|
|
@@ -794,7 +864,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
794
864
|
}
|
|
795
865
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
796
866
|
if (!shouldRetry) {
|
|
797
|
-
|
|
867
|
+
return this.throwWebUsbIoError(path, 'transferIn', error);
|
|
798
868
|
}
|
|
799
869
|
try {
|
|
800
870
|
await this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
@@ -808,7 +878,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
808
878
|
}
|
|
809
879
|
}
|
|
810
880
|
}
|
|
811
|
-
|
|
881
|
+
return this.throwWebUsbIoError(path, 'transferIn', lastError);
|
|
812
882
|
}
|
|
813
883
|
|
|
814
884
|
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
@@ -890,7 +960,8 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
890
960
|
}
|
|
891
961
|
);
|
|
892
962
|
return true;
|
|
893
|
-
} catch (
|
|
963
|
+
} catch (error) {
|
|
964
|
+
if (this.isWebUsbIoHardwareError(error)) throw error;
|
|
894
965
|
return false;
|
|
895
966
|
}
|
|
896
967
|
}
|
|
@@ -905,6 +976,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
905
976
|
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
906
977
|
logger: this.Log,
|
|
907
978
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
979
|
+
shouldRethrow: error => this.isWebUsbIoHardwareError(error),
|
|
908
980
|
});
|
|
909
981
|
}
|
|
910
982
|
|
|
@@ -982,7 +1054,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
982
1054
|
data: Record<string, unknown>,
|
|
983
1055
|
options?: TransportCallOptions
|
|
984
1056
|
) {
|
|
985
|
-
|
|
1057
|
+
try {
|
|
1058
|
+
return await this.callProtocolV2Usb(path, name, data, options);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
if (!(error instanceof ProtocolV2LinkError) || error.code !== 'io') throw error;
|
|
1061
|
+
const operation = error.message.includes(' USB write failed:') ? 'transferOut' : 'transferIn';
|
|
1062
|
+
return this.throwWebUsbIoError(path, operation, error);
|
|
1063
|
+
}
|
|
986
1064
|
}
|
|
987
1065
|
|
|
988
1066
|
/**
|