@onekeyfe/hd-transport-web-device 1.2.8-alpha.101 → 1.2.8-alpha.102

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.
@@ -1,5 +1,5 @@
1
1
  import transport from '@onekeyfe/hd-transport';
2
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
3
3
 
4
4
  import WebUsbTransport from '../src/webusb';
5
5
 
@@ -64,6 +64,75 @@ describe('WebUsbTransport protocol probe cache', () => {
64
64
  }
65
65
  );
66
66
 
67
+ test('uses the SDK 1.1.31 WebUSB sequence for a confirmed Protocol V1 device', async () => {
68
+ const webusb = new WebUsbTransport() as any;
69
+ const logger = { debug: jest.fn() };
70
+ const path = 'legacy-v1-device';
71
+ const device = {
72
+ opened: false,
73
+ configuration: { configurationValue: 1 },
74
+ configurations: [],
75
+ open: jest.fn().mockImplementation(() => {
76
+ device.opened = true;
77
+ return Promise.resolve();
78
+ }),
79
+ reset: jest.fn().mockResolvedValue(undefined),
80
+ selectConfiguration: jest.fn().mockResolvedValue(undefined),
81
+ claimInterface: jest.fn().mockResolvedValue(undefined),
82
+ clearHalt: jest.fn().mockResolvedValue(undefined),
83
+ };
84
+ webusb.Log = logger;
85
+ webusb.findDevice = jest.fn().mockResolvedValue(device);
86
+ webusb.getConnectedDevices = jest.fn().mockResolvedValue([]);
87
+
88
+ await webusb.connect(path, true, { expectedProtocol: 'V1' });
89
+
90
+ expect(device.open).toHaveBeenCalledTimes(1);
91
+ expect(device.selectConfiguration).toHaveBeenCalledWith(1);
92
+ expect(device.reset).toHaveBeenCalledTimes(1);
93
+ expect(device.claimInterface).toHaveBeenCalledWith(0);
94
+ expect(device.selectConfiguration.mock.invocationCallOrder[0]).toBeLessThan(
95
+ device.reset.mock.invocationCallOrder[0]
96
+ );
97
+ expect(device.reset.mock.invocationCallOrder[0]).toBeLessThan(
98
+ device.claimInterface.mock.invocationCallOrder[0]
99
+ );
100
+ expect(webusb.getConnectedDevices).not.toHaveBeenCalled();
101
+ expect(device.clearHalt).not.toHaveBeenCalled();
102
+ expect(logger.debug).toHaveBeenCalledWith(
103
+ '[WebUsbTransport][lifecycle]',
104
+ expect.objectContaining({
105
+ event: 'connect-attempt',
106
+ phase: 'start',
107
+ connectionStrategy: 'protocol-v1-legacy-1.1.31',
108
+ })
109
+ );
110
+ });
111
+
112
+ test('keeps the current WebUSB sequence when a cached V1 protocol is force-reprobed', async () => {
113
+ const webusb = new WebUsbTransport() as any;
114
+ const path = 'force-reprobe-device';
115
+ const device = {
116
+ opened: true,
117
+ configuration: { configurationValue: 1 },
118
+ configurations: [],
119
+ reset: jest.fn().mockResolvedValue(undefined),
120
+ selectConfiguration: jest.fn().mockResolvedValue(undefined),
121
+ claimInterface: jest.fn().mockResolvedValue(undefined),
122
+ clearHalt: jest.fn().mockResolvedValue(undefined),
123
+ };
124
+ webusb.Log = { debug: jest.fn() };
125
+ webusb.deviceProtocol.set(path, 'V1');
126
+ webusb.findDevice = jest.fn().mockResolvedValue(device);
127
+ webusb.getConnectedDevices = jest.fn().mockResolvedValue([]);
128
+
129
+ await webusb.connectToDevice(path, true, { forceProtocolDetection: true });
130
+
131
+ expect(device.reset).toHaveBeenCalledTimes(1);
132
+ expect(webusb.getConnectedDevices).toHaveBeenCalledTimes(1);
133
+ expect(device.clearHalt).toHaveBeenCalledTimes(2);
134
+ });
135
+
67
136
  test('reports claimInterface failures as WebUSB device access errors', async () => {
68
137
  const webusb = new WebUsbTransport();
69
138
  const path = 'connected-usb-device';
@@ -91,6 +160,129 @@ describe('WebUsbTransport protocol probe cache', () => {
91
160
  });
92
161
  });
93
162
 
163
+ test('does not retry a typed WebUSB handle error', async () => {
164
+ const webusb = new WebUsbTransport() as any;
165
+ const logger = { debug: jest.fn() };
166
+ const path = 'sensitive-device-path';
167
+ const accessError = ERRORS.TypedError(
168
+ HardwareErrorCode.WebUsbDeviceAccessError,
169
+ 'Unable to claim interface',
170
+ { operation: 'claimInterface' }
171
+ );
172
+ webusb.Log = logger;
173
+ webusb.connectToDevice = jest.fn().mockRejectedValue(accessError);
174
+
175
+ await expect(webusb.connect(path, true)).rejects.toBe(accessError);
176
+
177
+ expect(webusb.connectToDevice).toHaveBeenCalledTimes(1);
178
+ expect(logger.debug).toHaveBeenCalledWith(
179
+ '[WebUsbTransport][lifecycle]',
180
+ expect.objectContaining({
181
+ event: 'connect-attempt',
182
+ phase: 'error',
183
+ attempt: 1,
184
+ maxAttempts: 5,
185
+ willRetry: false,
186
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
187
+ })
188
+ );
189
+ expect(JSON.stringify(logger.debug.mock.calls)).not.toContain(path);
190
+ });
191
+
192
+ test('preserves the existing reconnect retry for a transient disconnect', async () => {
193
+ const webusb = new WebUsbTransport() as any;
194
+ const disconnected = ERRORS.TypedError(HardwareErrorCode.BridgeDeviceDisconnected);
195
+ webusb.Log = { debug: jest.fn() };
196
+ webusb.connectToDevice = jest
197
+ .fn()
198
+ .mockRejectedValueOnce(disconnected)
199
+ .mockResolvedValueOnce(undefined);
200
+
201
+ await expect(webusb.connect('device-path', true)).resolves.toBeUndefined();
202
+
203
+ expect(webusb.connectToDevice).toHaveBeenCalledTimes(2);
204
+ });
205
+
206
+ test('closes every owned handle after claimInterface fails across re-enumeration', async () => {
207
+ const webusb = new WebUsbTransport() as any;
208
+ const logger = { debug: jest.fn() };
209
+ const path = 'sensitive-device-path';
210
+ const claimError = Object.assign(new Error('Unable to claim interface'), {
211
+ name: 'NetworkError',
212
+ });
213
+ const usbInterface = {
214
+ interfaceNumber: 0,
215
+ claimed: false,
216
+ alternates: [],
217
+ };
218
+ const originalDevice = {
219
+ vendorId: 0x1209,
220
+ productId: 0x53c1,
221
+ serialNumber: path,
222
+ opened: false,
223
+ configuration: { configurationValue: 1, interfaces: [usbInterface] },
224
+ configurations: [],
225
+ open: jest.fn().mockImplementation(() => {
226
+ originalDevice.opened = true;
227
+ return Promise.resolve();
228
+ }),
229
+ reset: jest.fn().mockResolvedValue(undefined),
230
+ selectConfiguration: jest.fn().mockResolvedValue(undefined),
231
+ claimInterface: jest.fn().mockResolvedValue(undefined),
232
+ releaseInterface: jest.fn().mockResolvedValue(undefined),
233
+ close: jest.fn().mockImplementation(() => {
234
+ originalDevice.opened = false;
235
+ return Promise.resolve();
236
+ }),
237
+ clearHalt: jest.fn().mockResolvedValue(undefined),
238
+ };
239
+ const replacementDevice = {
240
+ ...originalDevice,
241
+ opened: false,
242
+ open: jest.fn().mockImplementation(() => {
243
+ replacementDevice.opened = true;
244
+ return Promise.resolve();
245
+ }),
246
+ reset: jest.fn().mockResolvedValue(undefined),
247
+ selectConfiguration: jest.fn().mockResolvedValue(undefined),
248
+ claimInterface: jest.fn().mockRejectedValue(claimError),
249
+ releaseInterface: jest.fn().mockResolvedValue(undefined),
250
+ close: jest.fn().mockImplementation(() => {
251
+ replacementDevice.opened = false;
252
+ return Promise.resolve();
253
+ }),
254
+ clearHalt: jest.fn().mockResolvedValue(undefined),
255
+ };
256
+ webusb.Log = logger;
257
+ webusb.usb = { getDevices: jest.fn().mockResolvedValue([replacementDevice]) };
258
+ webusb.deviceList = [{ path, device: originalDevice, commType: 'webusb' }];
259
+ webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
260
+
261
+ await expect(webusb.acquire({ path })).rejects.toMatchObject({
262
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
263
+ });
264
+
265
+ expect(originalDevice.claimInterface).not.toHaveBeenCalled();
266
+ expect(replacementDevice.claimInterface).toHaveBeenCalledTimes(1);
267
+ expect(originalDevice.releaseInterface).not.toHaveBeenCalled();
268
+ expect(replacementDevice.releaseInterface).not.toHaveBeenCalled();
269
+ expect(originalDevice.close).toHaveBeenCalledTimes(1);
270
+ expect(replacementDevice.close).toHaveBeenCalledTimes(1);
271
+ expect(originalDevice.opened).toBe(false);
272
+ expect(replacementDevice.opened).toBe(false);
273
+ expect(logger.debug).toHaveBeenCalledWith(
274
+ '[WebUsbTransport][lifecycle]',
275
+ expect.objectContaining({
276
+ event: 'handle-operation',
277
+ operation: 'releaseInterface',
278
+ phase: 'skipped',
279
+ reason: 'interface-not-claimed',
280
+ deviceRefId: expect.any(Number),
281
+ })
282
+ );
283
+ expect(JSON.stringify(logger.debug.mock.calls)).not.toContain(path);
284
+ });
285
+
94
286
  test('reports transferIn failures as WebUSB device access errors', async () => {
95
287
  const webusb = new WebUsbTransport() as any;
96
288
  const path = 'connected-usb-device';
@@ -193,6 +385,10 @@ describe('WebUsbTransport protocol probe cache', () => {
193
385
 
194
386
  await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
195
387
 
388
+ expect(webusb.connect).toHaveBeenCalledWith(path, true, {
389
+ expectedProtocol: 'V1',
390
+ forceProtocolDetection: undefined,
391
+ });
196
392
  expect(webusb.detectProtocol).not.toHaveBeenCalled();
197
393
  expect(webusb.deviceProtocol.get(path)).toBe('V1');
198
394
  expect(webusb.acquiredPaths.has(path)).toBe(true);
package/dist/index.d.ts CHANGED
@@ -12,8 +12,16 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
12
12
  device: USBDevice;
13
13
  protocolType?: ProtocolType;
14
14
  }
15
+ interface WebUsbConnectOptions {
16
+ expectedProtocol?: ProtocolType;
17
+ forceProtocolDetection?: boolean;
18
+ }
15
19
  declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
16
20
  private readonly identityProbe;
21
+ private readonly transportInstanceId;
22
+ private handleOperationSequence;
23
+ private deviceLogSequence;
24
+ private deviceLogIds;
17
25
  messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
18
26
  /** Protobuf schema for Protocol V2 transports. */
19
27
  messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
@@ -68,6 +76,13 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
68
76
  private stopResources;
69
77
  private assertRunning;
70
78
  private runHandleOperation;
79
+ private getInterfaceClaimed;
80
+ private getDeviceLogId;
81
+ private getHandleLogState;
82
+ private getDiagnosticError;
83
+ private logLifecycle;
84
+ private logSkippedHandleOperation;
85
+ private runLoggedHandleOperation;
71
86
  /**
72
87
  * Announce that a USB device left. Called from the module-scoped disconnect
73
88
  * listener, which is why it is public rather than inlined.
@@ -128,7 +143,8 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
128
143
  /**
129
144
  * Connect to device with retry mechanism
130
145
  */
131
- connect(path: string, first: boolean): Promise<undefined>;
146
+ connect(path: string, first: boolean, options?: WebUsbConnectOptions): Promise<undefined>;
147
+ private shouldUseLegacyV1Connection;
132
148
  /**
133
149
  * Discover vendor-class (0xFF) interface and its IN/OUT endpoint numbers from USB descriptors.
134
150
  * Falls back to legacy hardcoded values if no vendor interface is found.
@@ -138,7 +154,8 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
138
154
  * Connect to specific device.
139
155
  * Discovers interface/endpoint numbers from USB descriptors on first connection.
140
156
  */
141
- connectToDevice(path: string, first: boolean): Promise<undefined>;
157
+ connectToDevice(path: string, first: boolean, options?: WebUsbConnectOptions): Promise<undefined>;
158
+ private claimDeviceInterface;
142
159
  private assertHandleAvailable;
143
160
  private claimNormalHandle;
144
161
  private releaseNormalHandles;
@@ -146,6 +163,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
146
163
  private claimIdentityProbeHandle;
147
164
  private releaseIdentityProbeHandle;
148
165
  private closeOpenDevice;
166
+ private closeDeviceHandle;
149
167
  private clearEndpointHalt;
150
168
  /**
151
169
  * With the protocol cache surviving release(), deviceProtocol presence no