@onekeyfe/hd-transport-web-device 1.2.0-alpha.24 → 1.2.0-alpha.26

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,6 +1,6 @@
1
1
  import transport, {
2
+ PROTOCOL_V2_CHANNEL_USB,
2
3
  ProtocolV2,
3
- ProtocolV2FrameAssembler,
4
4
  ProtocolV2LinkError,
5
5
  } from '@onekeyfe/hd-transport';
6
6
 
@@ -20,6 +20,30 @@ const schema = {
20
20
  };
21
21
 
22
22
  describe('WebUsbTransport Protocol V2 timeout recovery', () => {
23
+ test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
24
+ const webusb = new WebUsbTransport() as any;
25
+ webusb.invalidateAllProtocolV2UsbLinks = jest.fn().mockResolvedValue(undefined);
26
+ const schemaSource = JSON.stringify(schema);
27
+
28
+ webusb.configureProtocolV2(schemaSource);
29
+ webusb.configureProtocolV2(schemaSource);
30
+
31
+ expect(webusb.invalidateAllProtocolV2UsbLinks).not.toHaveBeenCalled();
32
+
33
+ webusb.configureProtocolV2(
34
+ JSON.stringify({
35
+ ...schema,
36
+ nested: {
37
+ ...schema.nested,
38
+ Failure: { fields: { message: { type: 'string', id: 1 } } },
39
+ },
40
+ })
41
+ );
42
+ expect(webusb.invalidateAllProtocolV2UsbLinks).toHaveBeenCalledWith(
43
+ 'Protocol V2 schema reconfigured'
44
+ );
45
+ });
46
+
23
47
  test('resets the connection between a failed V1 probe and the V2 probe', async () => {
24
48
  const webusb = new WebUsbTransport() as any;
25
49
  const path = 'pro2-webusb';
@@ -80,20 +104,72 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
80
104
  const path = 'pro2-webusb';
81
105
  webusb.messages = transport.parseConfigure(schema);
82
106
  webusb.messagesV2 = transport.parseConfigure(schema);
83
- webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
84
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
85
- webusb.receiveProtocolV2Frame = jest.fn(() => new Promise<void>(() => {}));
86
- webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
87
- webusb.protocolV2Sessions.delete(path);
88
- webusb.protocolV2Assemblers.get(path)?.reset();
89
- });
107
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
108
+ webusb.readProtocolV2UsbPacket = jest.fn(() => new Promise<void>(() => {}));
109
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
110
+ webusb.resetConnectionAfterProbe = jest.fn();
111
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
90
112
 
91
113
  await expect(
92
114
  webusb.callProtocolV2(path, 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
93
115
  ).rejects.toThrow('timeout');
94
116
 
95
- expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
96
- expect(webusb.protocolV2Sessions.has(path)).toBe(false);
117
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
118
+ path,
119
+ expect.stringContaining('timeout')
120
+ );
121
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
122
+ });
123
+
124
+ test('does not reconnect inside a Protocol V2 frame read after a USB I/O failure', async () => {
125
+ const webusb = new WebUsbTransport() as any;
126
+ const path = 'pro2-webusb';
127
+ webusb.messages = transport.parseConfigure(schema);
128
+ webusb.messagesV2 = transport.parseConfigure(schema);
129
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
130
+ webusb.readProtocolV2UsbPacket = jest
131
+ .fn()
132
+ .mockRejectedValue(new Error('NetworkError: transferIn device disconnected'));
133
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
134
+ webusb.resetConnectionAfterProbe = jest.fn();
135
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
136
+
137
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })).rejects.toThrow(
138
+ 'NetworkError'
139
+ );
140
+
141
+ expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
142
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
143
+ path,
144
+ expect.stringContaining('NetworkError')
145
+ );
146
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
147
+ });
148
+
149
+ test('rejects an active Protocol V2 read without reconnecting after release', async () => {
150
+ const webusb = new WebUsbTransport() as any;
151
+ const path = 'pro2-webusb';
152
+ let markReadStarted: () => void = () => undefined;
153
+ const readStarted = new Promise<void>(resolve => {
154
+ markReadStarted = resolve;
155
+ });
156
+ webusb.messages = transport.parseConfigure(schema);
157
+ webusb.messagesV2 = transport.parseConfigure(schema);
158
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
159
+ webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
160
+ markReadStarted();
161
+ return new Promise<void>(() => {});
162
+ });
163
+ webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
164
+ webusb.connect = jest.fn();
165
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
166
+
167
+ const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
168
+ await readStarted;
169
+ await webusb.release(path);
170
+
171
+ await expect(call).rejects.toThrow('WebUSB transport released');
172
+ expect(webusb.connect).not.toHaveBeenCalled();
97
173
  });
98
174
 
99
175
  test.each(['router', 'packet-source', 'ack-sequence', 'response-sequence', 'frame'] as const)(
@@ -103,32 +179,34 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
103
179
  const path = 'pro2-webusb';
104
180
  webusb.messages = transport.parseConfigure(schema);
105
181
  webusb.messagesV2 = transport.parseConfigure(schema);
106
- webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
107
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
182
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
108
183
  const recoveredResponse = ProtocolV2.encodeFrame(
109
184
  { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
110
185
  'Success',
111
186
  { message: 'recovered' },
112
187
  { seq: 1 }
113
188
  );
114
- webusb.receiveProtocolV2Frame = jest
189
+ webusb.readProtocolV2UsbPacket = jest
115
190
  .fn()
116
191
  .mockRejectedValueOnce(
117
192
  new ProtocolV2LinkError(code, `Protocol V2 ${code} validation failed`)
118
193
  )
119
194
  .mockResolvedValue(recoveredResponse);
120
- webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
121
- webusb.protocolV2Sessions.delete(path);
122
- webusb.protocolV2Assemblers.get(path)?.reset();
123
- });
195
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
196
+ webusb.resetConnectionAfterProbe = jest.fn();
197
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
124
198
 
125
199
  await expect(webusb.callProtocolV2(path, 'Ping', { message: 'mismatch' })).rejects.toThrow(
126
200
  `${code} validation failed`
127
201
  );
128
202
 
129
- expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
130
- expect(webusb.protocolV2Sessions.has(path)).toBe(false);
203
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
204
+ path,
205
+ expect.stringContaining(`${code} validation failed`)
206
+ );
207
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
131
208
 
209
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test reconnect');
132
210
  await expect(
133
211
  webusb.callProtocolV2(path, 'Ping', { message: 'after-reset' })
134
212
  ).resolves.toMatchObject({
@@ -141,39 +219,53 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
141
219
  test('does not discard buffered Protocol V2 frames before each call', async () => {
142
220
  const webusb = new WebUsbTransport() as any;
143
221
  const path = 'pro2-webusb';
144
- const assembler = new ProtocolV2FrameAssembler();
145
- const reset = jest.spyOn(assembler, 'reset');
146
- let responseSequence = 0;
147
222
  webusb.messages = transport.parseConfigure(schema);
148
223
  webusb.messagesV2 = transport.parseConfigure(schema);
149
- webusb.protocolV2Assemblers.set(path, assembler);
150
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
151
- webusb.receiveProtocolV2Frame = jest.fn().mockImplementation(() => {
152
- responseSequence += 1;
153
- const response = ProtocolV2.encodeFrame(
154
- { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
155
- 'Success',
156
- { message: 'ok' },
157
- { seq: responseSequence }
158
- );
159
- return Promise.resolve(response);
160
- });
224
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
225
+ const firstResponse = ProtocolV2.encodeFrame(
226
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
227
+ 'Success',
228
+ { message: 'first' },
229
+ { router: PROTOCOL_V2_CHANNEL_USB, seq: 1 }
230
+ );
231
+ const secondResponse = ProtocolV2.encodeFrame(
232
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
233
+ 'Success',
234
+ { message: 'second' },
235
+ { router: PROTOCOL_V2_CHANNEL_USB, seq: 2 }
236
+ );
237
+ const coalescedResponses = new Uint8Array(firstResponse.length + secondResponse.length);
238
+ coalescedResponses.set(firstResponse);
239
+ coalescedResponses.set(secondResponse, firstResponse.length);
240
+ webusb.readProtocolV2UsbPacket = jest.fn().mockResolvedValue(coalescedResponses);
241
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
242
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
161
243
 
162
- await webusb.callProtocolV2(path, 'Ping', { message: 'first' });
163
- await webusb.callProtocolV2(path, 'Ping', { message: 'second' });
244
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'first' })).resolves.toMatchObject({
245
+ type: 'Success',
246
+ message: { message: 'first' },
247
+ });
248
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'second' })).resolves.toMatchObject(
249
+ {
250
+ type: 'Success',
251
+ message: { message: 'second' },
252
+ }
253
+ );
164
254
 
165
- expect(reset).not.toHaveBeenCalled();
255
+ expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
166
256
  });
167
257
 
168
258
  test('keeps queued Protocol V2 read timeouts scoped to each call', async () => {
169
259
  const webusb = new WebUsbTransport() as any;
170
260
  const path = 'pro2-webusb';
171
261
  let responseSequence = 0;
262
+ const readTimeouts: number[] = [];
172
263
  webusb.messages = transport.parseConfigure(schema);
173
264
  webusb.messagesV2 = transport.parseConfigure(schema);
174
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
175
- webusb.receiveProtocolV2Frame = jest.fn().mockImplementation(() => {
265
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
266
+ webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation((_path, context) => {
176
267
  responseSequence += 1;
268
+ readTimeouts.push(context.timeoutMs);
177
269
  return Promise.resolve(
178
270
  ProtocolV2.encodeFrame(
179
271
  { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
@@ -183,14 +275,14 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
183
275
  )
184
276
  );
185
277
  });
278
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
279
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
186
280
 
187
281
  await Promise.all([
188
282
  webusb.callProtocolV2(path, 'Ping', { message: 'long' }, { timeoutMs: 1_000 }),
189
283
  webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
190
284
  ]);
191
285
 
192
- expect(
193
- webusb.receiveProtocolV2Frame.mock.calls.map(([, timeoutMs]: unknown[]) => timeoutMs)
194
- ).toEqual([1_000, 25]);
286
+ expect(readTimeouts).toEqual([1_000, 25]);
195
287
  });
196
288
  });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _onekeyfe_hd_transport from '@onekeyfe/hd-transport';
2
- import _onekeyfe_hd_transport__default, { AcquireInput, TransportCallOptions, ProtocolType, OneKeyDeviceInfoBase, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
2
+ import _onekeyfe_hd_transport__default, { ProtocolV2UsbTransportBase, AcquireInput, TransportCallOptions, ProtocolV2Schemas, ProtocolV2CallContext, ProtocolType, OneKeyDeviceInfoBase, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
3
3
  import { Deferred } from '@onekeyfe/hd-shared';
4
4
  import { DesktopAPI } from '@onekeyfe/hd-transport-electron';
5
5
  import EventEmitter from 'events';
@@ -12,19 +12,14 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
12
12
  device: USBDevice;
13
13
  protocolType?: ProtocolType;
14
14
  }
15
- declare class WebUsbTransport {
15
+ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
16
16
  messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
17
17
  /** Protobuf schema for Protocol V2 transports. */
18
18
  messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
19
+ private protocolV2SchemaSource;
19
20
  /** Per-path protocol type detected by active wire-level probe. */
20
21
  private deviceProtocol;
21
22
  private deviceProtocolHints;
22
- /** Per-device Protocol V2 assembler that retains extra frames from one read. */
23
- private protocolV2Assemblers;
24
- /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
25
- private protocolV2Sessions;
26
- /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
27
- private protocolV2Sequences;
28
23
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
29
24
  private deviceEndpoints;
30
25
  /**
@@ -46,6 +41,7 @@ declare class WebUsbTransport {
46
41
  configurationId: number;
47
42
  endpointId: number;
48
43
  interfaceId: number;
44
+ constructor();
49
45
  /**
50
46
  * Initialize WebUSB transport
51
47
  */
@@ -120,6 +116,7 @@ declare class WebUsbTransport {
120
116
  private transferOutWithRetry;
121
117
  private transferOutOnce;
122
118
  private transferInWithRetry;
119
+ private transferInOnce;
123
120
  private resetConnectionAfterProbe;
124
121
  private withProtocolReadTimeout;
125
122
  private probeProtocolV1;
@@ -136,7 +133,6 @@ declare class WebUsbTransport {
136
133
  * Decoding: Protocol V2 frame → messageTypeId + pb bytes → protobuf message
137
134
  */
138
135
  private callProtocolV2;
139
- private receiveProtocolV2Frame;
140
136
  /**
141
137
  * Receive data from device
142
138
  */
@@ -145,6 +141,13 @@ declare class WebUsbTransport {
145
141
  * Release device
146
142
  */
147
143
  release(path: string): Promise<void>;
144
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
145
+ protected getProtocolV2UsbLogger(): any;
146
+ protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
147
+ protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
148
+ protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
149
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
150
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
148
151
  /**
149
152
  * Expose the detected protocol type for a given device path.
150
153
  * Used by upper layers (e.g. TransportManager) to select the correct schema.
package/dist/index.js CHANGED
@@ -64,13 +64,15 @@ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
64
64
  function inferProtocolHintFromDeviceName$1(name) {
65
65
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
66
66
  }
67
- class WebUsbTransport {
67
+ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
68
68
  constructor() {
69
+ super({
70
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
71
+ maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
72
+ logPrefix: 'ProtocolV2 WebUSB',
73
+ });
69
74
  this.deviceProtocol = new Map();
70
75
  this.deviceProtocolHints = new Map();
71
- this.protocolV2Assemblers = new Map();
72
- this.protocolV2Sessions = new Map();
73
- this.protocolV2Sequences = new Map();
74
76
  this.deviceEndpoints = new Map();
75
77
  this.mockSerialPaths = new WeakMap();
76
78
  this.mockSerialCounter = 0;
@@ -96,8 +98,18 @@ class WebUsbTransport {
96
98
  this.messages = messages;
97
99
  }
98
100
  configureProtocolV2(signedData) {
101
+ var _a;
102
+ const schemaSource = typeof signedData === 'string'
103
+ ? signedData
104
+ : (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
105
+ if (schemaSource === this.protocolV2SchemaSource)
106
+ return;
107
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
99
108
  this.messagesV2 = parseConfigure$1(signedData);
100
- this.protocolV2Sessions.clear();
109
+ this.protocolV2SchemaSource = schemaSource;
110
+ if (!hadProtocolV2Schema)
111
+ return;
112
+ this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] schema link cleanup failed:', error); });
101
113
  }
102
114
  promptDeviceAccess() {
103
115
  return __awaiter(this, void 0, void 0, function* () {
@@ -163,6 +175,7 @@ class WebUsbTransport {
163
175
  if (!input.path)
164
176
  return;
165
177
  try {
178
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
166
179
  yield this.closeOpenDevice(input.path);
167
180
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
168
181
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
@@ -289,7 +302,7 @@ class WebUsbTransport {
289
302
  };
290
303
  }
291
304
  connectToDevice(path, first) {
292
- var _a, _b;
305
+ var _a;
293
306
  return __awaiter(this, void 0, void 0, function* () {
294
307
  let device = yield this.findDevice(path);
295
308
  if (!device.opened) {
@@ -313,33 +326,30 @@ class WebUsbTransport {
313
326
  }
314
327
  const endpoints = this.discoverEndpoints(device);
315
328
  this.deviceEndpoints.set(path, endpoints);
316
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
317
- this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
318
329
  yield device.claimInterface(endpoints.interfaceNumber);
319
330
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
320
331
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
321
332
  });
322
333
  }
323
334
  closeOpenDevice(path) {
324
- var _a, _b, _c, _d, _e;
335
+ var _a, _b, _c, _d;
325
336
  return __awaiter(this, void 0, void 0, function* () {
326
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
327
- const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
337
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
328
338
  if (!(current === null || current === void 0 ? void 0 : current.opened))
329
339
  return;
330
340
  const endpoints = this.deviceEndpoints.get(path);
331
- const ifaceNum = (_c = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _c !== void 0 ? _c : this.interfaceId;
341
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
332
342
  try {
333
343
  yield current.releaseInterface(ifaceNum);
334
344
  }
335
345
  catch (error) {
336
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
346
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
337
347
  }
338
348
  try {
339
349
  yield current.close();
340
350
  }
341
351
  catch (error) {
342
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[WebUsbTransport] close before reconnect failed:', error);
352
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
343
353
  }
344
354
  });
345
355
  }
@@ -501,16 +511,28 @@ class WebUsbTransport {
501
511
  throw lastError;
502
512
  });
503
513
  }
514
+ transferInOnce(path, length) {
515
+ var _a;
516
+ return __awaiter(this, void 0, void 0, function* () {
517
+ const device = yield this.findDevice(path);
518
+ if (!device.opened) {
519
+ throw new Error('USBDevice is not open for transferIn');
520
+ }
521
+ const endpoints = this.deviceEndpoints.get(path);
522
+ const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
523
+ const result = yield device.transferIn(endpointIn, length);
524
+ return this.getTransferInData(result);
525
+ });
526
+ }
504
527
  resetConnectionAfterProbe(path) {
505
- var _a, _b;
528
+ var _a;
506
529
  return __awaiter(this, void 0, void 0, function* () {
507
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
508
- this.protocolV2Sessions.delete(path);
530
+ yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
509
531
  try {
510
532
  const device = yield this.findDevice(path);
511
533
  if (device.opened) {
512
534
  const endpoints = this.deviceEndpoints.get(path);
513
- const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
535
+ const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
514
536
  try {
515
537
  yield device.releaseInterface(ifaceNum);
516
538
  }
@@ -584,7 +606,7 @@ class WebUsbTransport {
584
606
  return false;
585
607
  }
586
608
  return transport.probeProtocolV2({
587
- call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
609
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
588
610
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
589
611
  logger: this.Log,
590
612
  logPrefix: 'ProtocolV2 WebUSB',
@@ -634,74 +656,9 @@ class WebUsbTransport {
634
656
  return check$1.call(jsonData);
635
657
  });
636
658
  }
637
- callProtocolV2(path, name, data, options, resetOnError = true) {
659
+ callProtocolV2(path, name, data, options) {
638
660
  return __awaiter(this, void 0, void 0, function* () {
639
- const protocolV1Messages = this.messages;
640
- if (!this.messagesV2) {
641
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
642
- }
643
- if (!protocolV1Messages) {
644
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
645
- }
646
- let session = this.protocolV2Sessions.get(path);
647
- if (!session) {
648
- let sequenceCursor = this.protocolV2Sequences.get(path);
649
- if (!sequenceCursor) {
650
- sequenceCursor = new transport.ProtocolV2SequenceCursor();
651
- this.protocolV2Sequences.set(path, sequenceCursor);
652
- }
653
- session = new transport.ProtocolV2Session({
654
- schemas: {
655
- protocolV1: protocolV1Messages,
656
- protocolV2: this.messagesV2,
657
- },
658
- router: transport.PROTOCOL_V2_CHANNEL_USB,
659
- sequenceCursor,
660
- writeFrame: (frame) => this.transferOutOnce(path, frame),
661
- readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
662
- logger: this.Log,
663
- logPrefix: 'ProtocolV2 WebUSB',
664
- createTimeoutError: (messageName, timeoutMs) => new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
665
- });
666
- this.protocolV2Sessions.set(path, session);
667
- }
668
- try {
669
- return yield session.call(name, data, options);
670
- }
671
- catch (error) {
672
- if (resetOnError && (transport.isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
673
- try {
674
- yield this.resetConnectionAfterProbe(path);
675
- }
676
- catch (resetError) {
677
- this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
678
- }
679
- }
680
- throw error;
681
- }
682
- });
683
- }
684
- receiveProtocolV2Frame(path, timeoutMs) {
685
- return __awaiter(this, void 0, void 0, function* () {
686
- let assembler = this.protocolV2Assemblers.get(path);
687
- if (!assembler) {
688
- assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
689
- this.protocolV2Assemblers.set(path, assembler);
690
- }
691
- let frame = assembler.push(new Uint8Array(0));
692
- const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
693
- while (!frame) {
694
- const cancelToken = { cancelled: false };
695
- const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
696
- const dataView = deadline
697
- ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
698
- cancelToken.cancelled = true;
699
- })
700
- : yield transferIn;
701
- const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
702
- frame = assembler.push(bytes);
703
- }
704
- return frame;
661
+ return this.callProtocolV2Usb(path, name, data, options);
705
662
  });
706
663
  }
707
664
  receiveData(path, timeoutMs) {
@@ -742,21 +699,50 @@ class WebUsbTransport {
742
699
  });
743
700
  }
744
701
  release(path) {
745
- var _a, _b;
746
702
  return __awaiter(this, void 0, void 0, function* () {
747
- const device = yield this.findDevice(path);
748
- const endpoints = this.deviceEndpoints.get(path);
749
- const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
750
- yield device.releaseInterface(ifaceNum);
751
- yield device.close();
703
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
704
+ yield this.closeOpenDevice(path);
752
705
  this.deviceProtocol.delete(path);
753
706
  this.deviceProtocolHints.delete(path);
754
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
755
- this.protocolV2Assemblers.delete(path);
756
- this.protocolV2Sessions.delete(path);
757
707
  this.deviceEndpoints.delete(path);
758
708
  });
759
709
  }
710
+ getProtocolV2UsbSchemas() {
711
+ if (!this.messages || !this.messagesV2) {
712
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
713
+ }
714
+ return {
715
+ protocolV1: this.messages,
716
+ protocolV2: this.messagesV2,
717
+ };
718
+ }
719
+ getProtocolV2UsbLogger() {
720
+ return this.Log;
721
+ }
722
+ writeProtocolV2UsbPacket(path, frame, _context) {
723
+ return __awaiter(this, void 0, void 0, function* () {
724
+ yield this.transferOutOnce(path, frame);
725
+ });
726
+ }
727
+ readProtocolV2UsbPacket(path, _context) {
728
+ return __awaiter(this, void 0, void 0, function* () {
729
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
730
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
731
+ });
732
+ }
733
+ resetProtocolV2UsbNativeLink(path, _reason) {
734
+ return __awaiter(this, void 0, void 0, function* () {
735
+ yield this.closeOpenDevice(path);
736
+ });
737
+ }
738
+ onProtocolV2UsbLinkInvalidated(path, reason) {
739
+ var _a;
740
+ this.deviceProtocol.delete(path);
741
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
742
+ }
743
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
744
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
745
+ }
760
746
  getProtocolType(path) {
761
747
  return this.deviceProtocol.get(path);
762
748
  }
package/dist/webusb.d.ts CHANGED
@@ -1,19 +1,17 @@
1
1
  /// <reference types="w3c-web-usb" />
2
- import transport from '@onekeyfe/hd-transport';
3
- import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, TransportCallOptions } from '@onekeyfe/hd-transport';
2
+ import transport, { ProtocolV2UsbTransportBase } from '@onekeyfe/hd-transport';
3
+ import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, ProtocolV2CallContext, ProtocolV2Schemas, TransportCallOptions } from '@onekeyfe/hd-transport';
4
4
  export interface DeviceInfo extends OneKeyDeviceInfoBase {
5
5
  path: string;
6
6
  device: USBDevice;
7
7
  protocolType?: ProtocolType;
8
8
  }
9
- export default class WebUsbTransport {
9
+ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
10
10
  messages: ReturnType<typeof transport.parseConfigure> | undefined;
11
11
  messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
12
+ private protocolV2SchemaSource;
12
13
  private deviceProtocol;
13
14
  private deviceProtocolHints;
14
- private protocolV2Assemblers;
15
- private protocolV2Sessions;
16
- private protocolV2Sequences;
17
15
  private deviceEndpoints;
18
16
  private mockSerialPaths;
19
17
  private mockSerialCounter;
@@ -26,6 +24,7 @@ export default class WebUsbTransport {
26
24
  configurationId: number;
27
25
  endpointId: number;
28
26
  interfaceId: number;
27
+ constructor();
29
28
  init(logger: any): void;
30
29
  configure(signedData: any): void;
31
30
  configureProtocolV2(signedData: any): void;
@@ -53,6 +52,7 @@ export default class WebUsbTransport {
53
52
  private transferOutWithRetry;
54
53
  private transferOutOnce;
55
54
  private transferInWithRetry;
55
+ private transferInOnce;
56
56
  private resetConnectionAfterProbe;
57
57
  private withProtocolReadTimeout;
58
58
  private probeProtocolV1;
@@ -60,9 +60,15 @@ export default class WebUsbTransport {
60
60
  call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
61
61
  private callProtocolV1;
62
62
  private callProtocolV2;
63
- private receiveProtocolV2Frame;
64
63
  receiveData(path: string, timeoutMs?: number): Promise<string>;
65
64
  release(path: string): Promise<void>;
65
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
66
+ protected getProtocolV2UsbLogger(): any;
67
+ protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
68
+ protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
69
+ protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
70
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
71
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
66
72
  getProtocolType(path: string): ProtocolType | undefined;
67
73
  }
68
74
  //# sourceMappingURL=webusb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAaN,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAsBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAaD,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,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;IAGpE,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,oBAAoB,CAAoD;IAGhF,OAAO,CAAC,kBAAkB,CAA6C;IAGvE,OAAO,CAAC,mBAAmB,CAAoD;IAG/E,OAAO,CAAC,eAAe,CAA2C;IAMlE,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;IAK3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAU7B,kBAAkB;IAmBlB,SAAS;IAQf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAgCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IA6DtB,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;YAmCpC,eAAe;YAmBf,iBAAiB;IAezB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAiCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,yBAAyB;YAwBzB,uBAAuB;YA0CvB,eAAe;YAaf,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;YA8BlB,cAAc;YAkCd,cAAc;YA2Dd,sBAAsB;IAqC9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAkB1B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
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.0-alpha.24",
3
+ "version": "1.2.0-alpha.26",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -20,13 +20,13 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.24",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.24"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.26",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.26"
25
25
  },
26
26
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.24",
27
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.26",
28
28
  "@types/w3c-web-usb": "^1.0.6",
29
29
  "@types/web-bluetooth": "^0.0.17"
30
30
  },
31
- "gitHead": "43663337d94350430cc507c74d6eec5a076bd4e9"
31
+ "gitHead": "1401fef3eb93f5a83ed5e8375b99181b7aaa6054"
32
32
  }
package/src/webusb.ts CHANGED
@@ -6,11 +6,8 @@ import transport, {
6
6
  PROTOCOL_V1_USB_PACKET_SIZE,
7
7
  PROTOCOL_V2_CHANNEL_USB,
8
8
  PROTOCOL_V2_FRAME_MAX_BYTES,
9
- ProtocolV2FrameAssembler,
10
9
  ProtocolV2LinkError,
11
- ProtocolV2SequenceCursor,
12
- ProtocolV2Session,
13
- isProtocolV2LinkError,
10
+ ProtocolV2UsbTransportBase,
14
11
  probeProtocolV2 as probeProtocolV2Helper,
15
12
  } from '@onekeyfe/hd-transport';
16
13
  import {
@@ -28,6 +25,8 @@ import type {
28
25
  AcquireInput,
29
26
  OneKeyDeviceInfoBase,
30
27
  ProtocolType,
28
+ ProtocolV2CallContext,
29
+ ProtocolV2Schemas,
31
30
  TransportCallOptions,
32
31
  } from '@onekeyfe/hd-transport';
33
32
 
@@ -68,26 +67,19 @@ interface TransferCancelToken {
68
67
  cancelled: boolean;
69
68
  }
70
69
 
71
- export default class WebUsbTransport {
70
+ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
72
71
  messages: ReturnType<typeof transport.parseConfigure> | undefined;
73
72
 
74
73
  /** Protobuf schema for Protocol V2 transports. */
75
74
  messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
76
75
 
76
+ private protocolV2SchemaSource: string | undefined;
77
+
77
78
  /** Per-path protocol type detected by active wire-level probe. */
78
79
  private deviceProtocol: Map<string, ProtocolType> = new Map();
79
80
 
80
81
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
81
82
 
82
- /** Per-device Protocol V2 assembler that retains extra frames from one read. */
83
- private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
84
-
85
- /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
86
- private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
87
-
88
- /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
89
- private protocolV2Sequences: Map<string, ProtocolV2SequenceCursor> = new Map();
90
-
91
83
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
92
84
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
93
85
 
@@ -121,6 +113,14 @@ export default class WebUsbTransport {
121
113
 
122
114
  interfaceId = INTERFACE_ID;
123
115
 
116
+ constructor() {
117
+ super({
118
+ router: PROTOCOL_V2_CHANNEL_USB,
119
+ maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
120
+ logPrefix: 'ProtocolV2 WebUSB',
121
+ });
122
+ }
123
+
124
124
  /**
125
125
  * Initialize WebUSB transport
126
126
  */
@@ -150,8 +150,20 @@ export default class WebUsbTransport {
150
150
  * Cache the Protocol V2 protobuf schema.
151
151
  */
152
152
  configureProtocolV2(signedData: any) {
153
+ const schemaSource =
154
+ typeof signedData === 'string'
155
+ ? signedData
156
+ : JSON.stringify(signedData) ?? String(signedData);
157
+ if (schemaSource === this.protocolV2SchemaSource) return;
158
+
159
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
153
160
  this.messagesV2 = parseConfigure(signedData);
154
- this.protocolV2Sessions.clear();
161
+ this.protocolV2SchemaSource = schemaSource;
162
+ if (!hadProtocolV2Schema) return;
163
+
164
+ this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
165
+ this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
166
+ );
155
167
  }
156
168
 
157
169
  /**
@@ -240,6 +252,7 @@ export default class WebUsbTransport {
240
252
  async acquire(input: AcquireInput) {
241
253
  if (!input.path) return;
242
254
  try {
255
+ await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
243
256
  await this.closeOpenDevice(input.path);
244
257
  await this.connect(input.path ?? '', true);
245
258
  const deviceName = this.deviceList.find(device => device.path === input.path)?.device
@@ -447,16 +460,12 @@ export default class WebUsbTransport {
447
460
  // Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
448
461
  const endpoints = this.discoverEndpoints(device);
449
462
  this.deviceEndpoints.set(path, endpoints);
450
- this.protocolV2Assemblers.get(path)?.reset();
451
- this.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES));
452
-
453
463
  await device.claimInterface(endpoints.interfaceNumber);
454
464
  await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
455
465
  await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
456
466
  }
457
467
 
458
468
  private async closeOpenDevice(path: string) {
459
- this.protocolV2Assemblers.get(path)?.reset();
460
469
  const current = this.deviceList.find(device => device.path === path)?.device;
461
470
  if (!current?.opened) return;
462
471
 
@@ -651,9 +660,19 @@ export default class WebUsbTransport {
651
660
  throw lastError;
652
661
  }
653
662
 
663
+ private async transferInOnce(path: string, length: number): Promise<DataView> {
664
+ const device = await this.findDevice(path);
665
+ if (!device.opened) {
666
+ throw new Error('USBDevice is not open for transferIn');
667
+ }
668
+ const endpoints = this.deviceEndpoints.get(path);
669
+ const endpointIn = endpoints?.endpointIn ?? this.endpointId;
670
+ const result = await device.transferIn(endpointIn, length);
671
+ return this.getTransferInData(result);
672
+ }
673
+
654
674
  private async resetConnectionAfterProbe(path: string) {
655
- this.protocolV2Assemblers.get(path)?.reset();
656
- this.protocolV2Sessions.delete(path);
675
+ await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
657
676
 
658
677
  try {
659
678
  const device = await this.findDevice(path);
@@ -736,7 +755,7 @@ export default class WebUsbTransport {
736
755
  }
737
756
 
738
757
  return probeProtocolV2Helper({
739
- call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
758
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
740
759
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
741
760
  logger: this.Log,
742
761
  logPrefix: 'ProtocolV2 WebUSB',
@@ -818,93 +837,9 @@ export default class WebUsbTransport {
818
837
  path: string,
819
838
  name: string,
820
839
  data: Record<string, unknown>,
821
- options?: TransportCallOptions,
822
- resetOnError = true
840
+ options?: TransportCallOptions
823
841
  ) {
824
- const protocolV1Messages = this.messages;
825
- if (!this.messagesV2) {
826
- throw ERRORS.TypedError(
827
- HardwareErrorCode.TransportNotConfigured,
828
- 'Protocol V2 schema not configured'
829
- );
830
- }
831
- if (!protocolV1Messages) {
832
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
833
- }
834
-
835
- let session = this.protocolV2Sessions.get(path);
836
- if (!session) {
837
- let sequenceCursor = this.protocolV2Sequences.get(path);
838
- if (!sequenceCursor) {
839
- sequenceCursor = new ProtocolV2SequenceCursor();
840
- this.protocolV2Sequences.set(path, sequenceCursor);
841
- }
842
- session = new ProtocolV2Session({
843
- schemas: {
844
- protocolV1: protocolV1Messages,
845
- protocolV2: this.messagesV2,
846
- },
847
- router: PROTOCOL_V2_CHANNEL_USB,
848
- sequenceCursor,
849
- writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
850
- readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
851
- logger: this.Log,
852
- logPrefix: 'ProtocolV2 WebUSB',
853
- createTimeoutError: (messageName: string, timeoutMs: number) =>
854
- new ProtocolV2LinkError(
855
- 'response-timeout',
856
- `Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`
857
- ),
858
- });
859
- this.protocolV2Sessions.set(path, session);
860
- }
861
-
862
- try {
863
- return await session.call(name, data, options);
864
- } catch (error) {
865
- if (resetOnError && (isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
866
- try {
867
- await this.resetConnectionAfterProbe(path);
868
- } catch (resetError) {
869
- this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
870
- }
871
- }
872
- throw error;
873
- }
874
- }
875
-
876
- private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
877
- let assembler = this.protocolV2Assemblers.get(path);
878
- if (!assembler) {
879
- assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
880
- this.protocolV2Assemblers.set(path, assembler);
881
- }
882
-
883
- let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
884
- const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
885
-
886
- while (!frame) {
887
- const cancelToken = { cancelled: false };
888
- const transferIn = this.transferInWithRetry(path, PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
889
- const dataView = deadline
890
- ? await this.withProtocolReadTimeout(
891
- path,
892
- transferIn,
893
- Math.max(deadline - Date.now(), 1),
894
- 'V2',
895
- () => {
896
- cancelToken.cancelled = true;
897
- }
898
- )
899
- : await transferIn;
900
- const bytes = new Uint8Array(
901
- this.toArrayBuffer(
902
- dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
903
- )
904
- );
905
- frame = assembler.push(bytes);
906
- }
907
- return frame;
842
+ return this.callProtocolV2Usb(path, name, data, options);
908
843
  }
909
844
 
910
845
  /**
@@ -959,19 +894,63 @@ export default class WebUsbTransport {
959
894
  * Release device
960
895
  */
961
896
  async release(path: string) {
962
- const device: USBDevice = await this.findDevice(path);
963
- const endpoints = this.deviceEndpoints.get(path);
964
- const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
965
- await device.releaseInterface(ifaceNum);
966
- await device.close();
897
+ await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
898
+ await this.closeOpenDevice(path);
967
899
  this.deviceProtocol.delete(path);
968
900
  this.deviceProtocolHints.delete(path);
969
- this.protocolV2Assemblers.get(path)?.reset();
970
- this.protocolV2Assemblers.delete(path);
971
- this.protocolV2Sessions.delete(path);
972
901
  this.deviceEndpoints.delete(path);
973
902
  }
974
903
 
904
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
905
+ if (!this.messages || !this.messagesV2) {
906
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
907
+ }
908
+ return {
909
+ protocolV1: this.messages,
910
+ protocolV2: this.messagesV2,
911
+ };
912
+ }
913
+
914
+ protected getProtocolV2UsbLogger() {
915
+ return this.Log;
916
+ }
917
+
918
+ protected async writeProtocolV2UsbPacket(
919
+ path: string,
920
+ frame: Uint8Array,
921
+ _context: ProtocolV2CallContext
922
+ ): Promise<void> {
923
+ await this.transferOutOnce(path, frame);
924
+ }
925
+
926
+ protected async readProtocolV2UsbPacket(
927
+ path: string,
928
+ _context: ProtocolV2CallContext
929
+ ): Promise<Uint8Array> {
930
+ const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
931
+ return new Uint8Array(
932
+ this.toArrayBuffer(
933
+ dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
934
+ )
935
+ );
936
+ }
937
+
938
+ protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
939
+ await this.closeOpenDevice(path);
940
+ }
941
+
942
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
943
+ this.deviceProtocol.delete(path);
944
+ this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
945
+ }
946
+
947
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
948
+ return new ProtocolV2LinkError(
949
+ 'response-timeout',
950
+ `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
951
+ );
952
+ }
953
+
975
954
  /**
976
955
  * Expose the detected protocol type for a given device path.
977
956
  * Used by upper layers (e.g. TransportManager) to select the correct schema.