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

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
 
@@ -80,20 +80,72 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
80
80
  const path = 'pro2-webusb';
81
81
  webusb.messages = transport.parseConfigure(schema);
82
82
  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
- });
83
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
84
+ webusb.readProtocolV2UsbPacket = jest.fn(() => new Promise<void>(() => {}));
85
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
86
+ webusb.resetConnectionAfterProbe = jest.fn();
87
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
90
88
 
91
89
  await expect(
92
90
  webusb.callProtocolV2(path, 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
93
91
  ).rejects.toThrow('timeout');
94
92
 
95
- expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
96
- expect(webusb.protocolV2Sessions.has(path)).toBe(false);
93
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
94
+ path,
95
+ expect.stringContaining('timeout')
96
+ );
97
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
98
+ });
99
+
100
+ test('does not reconnect inside a Protocol V2 frame read after a USB I/O failure', async () => {
101
+ const webusb = new WebUsbTransport() as any;
102
+ const path = 'pro2-webusb';
103
+ webusb.messages = transport.parseConfigure(schema);
104
+ webusb.messagesV2 = transport.parseConfigure(schema);
105
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
106
+ webusb.readProtocolV2UsbPacket = jest
107
+ .fn()
108
+ .mockRejectedValue(new Error('NetworkError: transferIn device disconnected'));
109
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
110
+ webusb.resetConnectionAfterProbe = jest.fn();
111
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
112
+
113
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })).rejects.toThrow(
114
+ 'NetworkError'
115
+ );
116
+
117
+ expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
118
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
119
+ path,
120
+ expect.stringContaining('NetworkError')
121
+ );
122
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
123
+ });
124
+
125
+ test('rejects an active Protocol V2 read without reconnecting after release', async () => {
126
+ const webusb = new WebUsbTransport() as any;
127
+ const path = 'pro2-webusb';
128
+ let markReadStarted: () => void = () => undefined;
129
+ const readStarted = new Promise<void>(resolve => {
130
+ markReadStarted = resolve;
131
+ });
132
+ webusb.messages = transport.parseConfigure(schema);
133
+ webusb.messagesV2 = transport.parseConfigure(schema);
134
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
135
+ webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
136
+ markReadStarted();
137
+ return new Promise<void>(() => {});
138
+ });
139
+ webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
140
+ webusb.connect = jest.fn();
141
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
142
+
143
+ const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
144
+ await readStarted;
145
+ await webusb.release(path);
146
+
147
+ await expect(call).rejects.toThrow('WebUSB transport released');
148
+ expect(webusb.connect).not.toHaveBeenCalled();
97
149
  });
98
150
 
99
151
  test.each(['router', 'packet-source', 'ack-sequence', 'response-sequence', 'frame'] as const)(
@@ -103,32 +155,34 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
103
155
  const path = 'pro2-webusb';
104
156
  webusb.messages = transport.parseConfigure(schema);
105
157
  webusb.messagesV2 = transport.parseConfigure(schema);
106
- webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
107
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
158
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
108
159
  const recoveredResponse = ProtocolV2.encodeFrame(
109
160
  { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
110
161
  'Success',
111
162
  { message: 'recovered' },
112
163
  { seq: 1 }
113
164
  );
114
- webusb.receiveProtocolV2Frame = jest
165
+ webusb.readProtocolV2UsbPacket = jest
115
166
  .fn()
116
167
  .mockRejectedValueOnce(
117
168
  new ProtocolV2LinkError(code, `Protocol V2 ${code} validation failed`)
118
169
  )
119
170
  .mockResolvedValue(recoveredResponse);
120
- webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
121
- webusb.protocolV2Sessions.delete(path);
122
- webusb.protocolV2Assemblers.get(path)?.reset();
123
- });
171
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
172
+ webusb.resetConnectionAfterProbe = jest.fn();
173
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
124
174
 
125
175
  await expect(webusb.callProtocolV2(path, 'Ping', { message: 'mismatch' })).rejects.toThrow(
126
176
  `${code} validation failed`
127
177
  );
128
178
 
129
- expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
130
- expect(webusb.protocolV2Sessions.has(path)).toBe(false);
179
+ expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
180
+ path,
181
+ expect.stringContaining(`${code} validation failed`)
182
+ );
183
+ expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
131
184
 
185
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test reconnect');
132
186
  await expect(
133
187
  webusb.callProtocolV2(path, 'Ping', { message: 'after-reset' })
134
188
  ).resolves.toMatchObject({
@@ -141,39 +195,53 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
141
195
  test('does not discard buffered Protocol V2 frames before each call', async () => {
142
196
  const webusb = new WebUsbTransport() as any;
143
197
  const path = 'pro2-webusb';
144
- const assembler = new ProtocolV2FrameAssembler();
145
- const reset = jest.spyOn(assembler, 'reset');
146
- let responseSequence = 0;
147
198
  webusb.messages = transport.parseConfigure(schema);
148
199
  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
- });
200
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
201
+ const firstResponse = ProtocolV2.encodeFrame(
202
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
203
+ 'Success',
204
+ { message: 'first' },
205
+ { router: PROTOCOL_V2_CHANNEL_USB, seq: 1 }
206
+ );
207
+ const secondResponse = ProtocolV2.encodeFrame(
208
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
209
+ 'Success',
210
+ { message: 'second' },
211
+ { router: PROTOCOL_V2_CHANNEL_USB, seq: 2 }
212
+ );
213
+ const coalescedResponses = new Uint8Array(firstResponse.length + secondResponse.length);
214
+ coalescedResponses.set(firstResponse);
215
+ coalescedResponses.set(secondResponse, firstResponse.length);
216
+ webusb.readProtocolV2UsbPacket = jest.fn().mockResolvedValue(coalescedResponses);
217
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
218
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
161
219
 
162
- await webusb.callProtocolV2(path, 'Ping', { message: 'first' });
163
- await webusb.callProtocolV2(path, 'Ping', { message: 'second' });
220
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'first' })).resolves.toMatchObject({
221
+ type: 'Success',
222
+ message: { message: 'first' },
223
+ });
224
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'second' })).resolves.toMatchObject(
225
+ {
226
+ type: 'Success',
227
+ message: { message: 'second' },
228
+ }
229
+ );
164
230
 
165
- expect(reset).not.toHaveBeenCalled();
231
+ expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
166
232
  });
167
233
 
168
234
  test('keeps queued Protocol V2 read timeouts scoped to each call', async () => {
169
235
  const webusb = new WebUsbTransport() as any;
170
236
  const path = 'pro2-webusb';
171
237
  let responseSequence = 0;
238
+ const readTimeouts: number[] = [];
172
239
  webusb.messages = transport.parseConfigure(schema);
173
240
  webusb.messagesV2 = transport.parseConfigure(schema);
174
- webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
175
- webusb.receiveProtocolV2Frame = jest.fn().mockImplementation(() => {
241
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
242
+ webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation((_path, context) => {
176
243
  responseSequence += 1;
244
+ readTimeouts.push(context.timeoutMs);
177
245
  return Promise.resolve(
178
246
  ProtocolV2.encodeFrame(
179
247
  { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
@@ -183,14 +251,14 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
183
251
  )
184
252
  );
185
253
  });
254
+ webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
255
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
186
256
 
187
257
  await Promise.all([
188
258
  webusb.callProtocolV2(path, 'Ping', { message: 'long' }, { timeoutMs: 1_000 }),
189
259
  webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
190
260
  ]);
191
261
 
192
- expect(
193
- webusb.receiveProtocolV2Frame.mock.calls.map(([, timeoutMs]: unknown[]) => timeoutMs)
194
- ).toEqual([1_000, 25]);
262
+ expect(readTimeouts).toEqual([1_000, 25]);
195
263
  });
196
264
  });
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,13 @@ 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
19
  /** Per-path protocol type detected by active wire-level probe. */
20
20
  private deviceProtocol;
21
21
  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
22
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
29
23
  private deviceEndpoints;
30
24
  /**
@@ -46,6 +40,7 @@ declare class WebUsbTransport {
46
40
  configurationId: number;
47
41
  endpointId: number;
48
42
  interfaceId: number;
43
+ constructor();
49
44
  /**
50
45
  * Initialize WebUSB transport
51
46
  */
@@ -120,6 +115,7 @@ declare class WebUsbTransport {
120
115
  private transferOutWithRetry;
121
116
  private transferOutOnce;
122
117
  private transferInWithRetry;
118
+ private transferInOnce;
123
119
  private resetConnectionAfterProbe;
124
120
  private withProtocolReadTimeout;
125
121
  private probeProtocolV1;
@@ -136,7 +132,6 @@ declare class WebUsbTransport {
136
132
  * Decoding: Protocol V2 frame → messageTypeId + pb bytes → protobuf message
137
133
  */
138
134
  private callProtocolV2;
139
- private receiveProtocolV2Frame;
140
135
  /**
141
136
  * Receive data from device
142
137
  */
@@ -145,6 +140,13 @@ declare class WebUsbTransport {
145
140
  * Release device
146
141
  */
147
142
  release(path: string): Promise<void>;
143
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
144
+ protected getProtocolV2UsbLogger(): any;
145
+ protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
146
+ protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
147
+ protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
148
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
149
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
148
150
  /**
149
151
  * Expose the detected protocol type for a given device path.
150
152
  * 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;
@@ -97,7 +99,7 @@ class WebUsbTransport {
97
99
  }
98
100
  configureProtocolV2(signedData) {
99
101
  this.messagesV2 = parseConfigure$1(signedData);
100
- this.protocolV2Sessions.clear();
102
+ 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
103
  }
102
104
  promptDeviceAccess() {
103
105
  return __awaiter(this, void 0, void 0, function* () {
@@ -163,6 +165,7 @@ class WebUsbTransport {
163
165
  if (!input.path)
164
166
  return;
165
167
  try {
168
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
166
169
  yield this.closeOpenDevice(input.path);
167
170
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
168
171
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
@@ -289,7 +292,7 @@ class WebUsbTransport {
289
292
  };
290
293
  }
291
294
  connectToDevice(path, first) {
292
- var _a, _b;
295
+ var _a;
293
296
  return __awaiter(this, void 0, void 0, function* () {
294
297
  let device = yield this.findDevice(path);
295
298
  if (!device.opened) {
@@ -313,33 +316,30 @@ class WebUsbTransport {
313
316
  }
314
317
  const endpoints = this.discoverEndpoints(device);
315
318
  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
319
  yield device.claimInterface(endpoints.interfaceNumber);
319
320
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
320
321
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
321
322
  });
322
323
  }
323
324
  closeOpenDevice(path) {
324
- var _a, _b, _c, _d, _e;
325
+ var _a, _b, _c, _d;
325
326
  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;
327
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
328
328
  if (!(current === null || current === void 0 ? void 0 : current.opened))
329
329
  return;
330
330
  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;
331
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
332
332
  try {
333
333
  yield current.releaseInterface(ifaceNum);
334
334
  }
335
335
  catch (error) {
336
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
336
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
337
337
  }
338
338
  try {
339
339
  yield current.close();
340
340
  }
341
341
  catch (error) {
342
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[WebUsbTransport] close before reconnect failed:', error);
342
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
343
343
  }
344
344
  });
345
345
  }
@@ -501,16 +501,28 @@ class WebUsbTransport {
501
501
  throw lastError;
502
502
  });
503
503
  }
504
+ transferInOnce(path, length) {
505
+ var _a;
506
+ return __awaiter(this, void 0, void 0, function* () {
507
+ const device = yield this.findDevice(path);
508
+ if (!device.opened) {
509
+ throw new Error('USBDevice is not open for transferIn');
510
+ }
511
+ const endpoints = this.deviceEndpoints.get(path);
512
+ const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
513
+ const result = yield device.transferIn(endpointIn, length);
514
+ return this.getTransferInData(result);
515
+ });
516
+ }
504
517
  resetConnectionAfterProbe(path) {
505
- var _a, _b;
518
+ var _a;
506
519
  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);
520
+ yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
509
521
  try {
510
522
  const device = yield this.findDevice(path);
511
523
  if (device.opened) {
512
524
  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;
525
+ const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
514
526
  try {
515
527
  yield device.releaseInterface(ifaceNum);
516
528
  }
@@ -584,7 +596,7 @@ class WebUsbTransport {
584
596
  return false;
585
597
  }
586
598
  return transport.probeProtocolV2({
587
- call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
599
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
588
600
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
589
601
  logger: this.Log,
590
602
  logPrefix: 'ProtocolV2 WebUSB',
@@ -634,74 +646,9 @@ class WebUsbTransport {
634
646
  return check$1.call(jsonData);
635
647
  });
636
648
  }
637
- callProtocolV2(path, name, data, options, resetOnError = true) {
638
- 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) {
649
+ callProtocolV2(path, name, data, options) {
685
650
  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;
651
+ return this.callProtocolV2Usb(path, name, data, options);
705
652
  });
706
653
  }
707
654
  receiveData(path, timeoutMs) {
@@ -742,21 +689,50 @@ class WebUsbTransport {
742
689
  });
743
690
  }
744
691
  release(path) {
745
- var _a, _b;
746
692
  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();
693
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
694
+ yield this.closeOpenDevice(path);
752
695
  this.deviceProtocol.delete(path);
753
696
  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
697
  this.deviceEndpoints.delete(path);
758
698
  });
759
699
  }
700
+ getProtocolV2UsbSchemas() {
701
+ if (!this.messages || !this.messagesV2) {
702
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
703
+ }
704
+ return {
705
+ protocolV1: this.messages,
706
+ protocolV2: this.messagesV2,
707
+ };
708
+ }
709
+ getProtocolV2UsbLogger() {
710
+ return this.Log;
711
+ }
712
+ writeProtocolV2UsbPacket(path, frame, _context) {
713
+ return __awaiter(this, void 0, void 0, function* () {
714
+ yield this.transferOutOnce(path, frame);
715
+ });
716
+ }
717
+ readProtocolV2UsbPacket(path, _context) {
718
+ return __awaiter(this, void 0, void 0, function* () {
719
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
720
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
721
+ });
722
+ }
723
+ resetProtocolV2UsbNativeLink(path, _reason) {
724
+ return __awaiter(this, void 0, void 0, function* () {
725
+ yield this.closeOpenDevice(path);
726
+ });
727
+ }
728
+ onProtocolV2UsbLinkInvalidated(path, reason) {
729
+ var _a;
730
+ this.deviceProtocol.delete(path);
731
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
732
+ }
733
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
734
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
735
+ }
760
736
  getProtocolType(path) {
761
737
  return this.deviceProtocol.get(path);
762
738
  }
package/dist/webusb.d.ts CHANGED
@@ -1,19 +1,16 @@
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
12
  private deviceProtocol;
13
13
  private deviceProtocolHints;
14
- private protocolV2Assemblers;
15
- private protocolV2Sessions;
16
- private protocolV2Sequences;
17
14
  private deviceEndpoints;
18
15
  private mockSerialPaths;
19
16
  private mockSerialCounter;
@@ -26,6 +23,7 @@ export default class WebUsbTransport {
26
23
  configurationId: number;
27
24
  endpointId: number;
28
25
  interfaceId: number;
26
+ constructor();
29
27
  init(logger: any): void;
30
28
  configure(signedData: any): void;
31
29
  configureProtocolV2(signedData: any): void;
@@ -53,6 +51,7 @@ export default class WebUsbTransport {
53
51
  private transferOutWithRetry;
54
52
  private transferOutOnce;
55
53
  private transferInWithRetry;
54
+ private transferInOnce;
56
55
  private resetConnectionAfterProbe;
57
56
  private withProtocolReadTimeout;
58
57
  private probeProtocolV1;
@@ -60,9 +59,15 @@ export default class WebUsbTransport {
60
59
  call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
61
60
  private callProtocolV1;
62
61
  private callProtocolV2;
63
- private receiveProtocolV2Frame;
64
62
  receiveData(path: string, timeoutMs?: number): Promise<string>;
65
63
  release(path: string): Promise<void>;
64
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
65
+ protected getProtocolV2UsbLogger(): any;
66
+ protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
67
+ protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
68
+ protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
69
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
70
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
66
71
  getProtocolType(path: string): ProtocolType | undefined;
67
72
  }
68
73
  //# 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.25",
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.25",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.25"
25
25
  },
26
26
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.24",
27
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.25",
28
28
  "@types/w3c-web-usb": "^1.0.6",
29
29
  "@types/web-bluetooth": "^0.0.17"
30
30
  },
31
- "gitHead": "43663337d94350430cc507c74d6eec5a076bd4e9"
31
+ "gitHead": "20bb98b8530c509299b9e109d2b5ada7c6a6d034"
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,7 +67,7 @@ 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. */
@@ -79,15 +78,6 @@ export default class WebUsbTransport {
79
78
 
80
79
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
81
80
 
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
81
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
92
82
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
93
83
 
@@ -121,6 +111,14 @@ export default class WebUsbTransport {
121
111
 
122
112
  interfaceId = INTERFACE_ID;
123
113
 
114
+ constructor() {
115
+ super({
116
+ router: PROTOCOL_V2_CHANNEL_USB,
117
+ maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
118
+ logPrefix: 'ProtocolV2 WebUSB',
119
+ });
120
+ }
121
+
124
122
  /**
125
123
  * Initialize WebUSB transport
126
124
  */
@@ -151,7 +149,9 @@ export default class WebUsbTransport {
151
149
  */
152
150
  configureProtocolV2(signedData: any) {
153
151
  this.messagesV2 = parseConfigure(signedData);
154
- this.protocolV2Sessions.clear();
152
+ this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
153
+ this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
154
+ );
155
155
  }
156
156
 
157
157
  /**
@@ -240,6 +240,7 @@ export default class WebUsbTransport {
240
240
  async acquire(input: AcquireInput) {
241
241
  if (!input.path) return;
242
242
  try {
243
+ await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
243
244
  await this.closeOpenDevice(input.path);
244
245
  await this.connect(input.path ?? '', true);
245
246
  const deviceName = this.deviceList.find(device => device.path === input.path)?.device
@@ -447,16 +448,12 @@ export default class WebUsbTransport {
447
448
  // Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
448
449
  const endpoints = this.discoverEndpoints(device);
449
450
  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
451
  await device.claimInterface(endpoints.interfaceNumber);
454
452
  await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
455
453
  await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
456
454
  }
457
455
 
458
456
  private async closeOpenDevice(path: string) {
459
- this.protocolV2Assemblers.get(path)?.reset();
460
457
  const current = this.deviceList.find(device => device.path === path)?.device;
461
458
  if (!current?.opened) return;
462
459
 
@@ -651,9 +648,19 @@ export default class WebUsbTransport {
651
648
  throw lastError;
652
649
  }
653
650
 
651
+ private async transferInOnce(path: string, length: number): Promise<DataView> {
652
+ const device = await this.findDevice(path);
653
+ if (!device.opened) {
654
+ throw new Error('USBDevice is not open for transferIn');
655
+ }
656
+ const endpoints = this.deviceEndpoints.get(path);
657
+ const endpointIn = endpoints?.endpointIn ?? this.endpointId;
658
+ const result = await device.transferIn(endpointIn, length);
659
+ return this.getTransferInData(result);
660
+ }
661
+
654
662
  private async resetConnectionAfterProbe(path: string) {
655
- this.protocolV2Assemblers.get(path)?.reset();
656
- this.protocolV2Sessions.delete(path);
663
+ await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
657
664
 
658
665
  try {
659
666
  const device = await this.findDevice(path);
@@ -736,7 +743,7 @@ export default class WebUsbTransport {
736
743
  }
737
744
 
738
745
  return probeProtocolV2Helper({
739
- call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
746
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options),
740
747
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
741
748
  logger: this.Log,
742
749
  logPrefix: 'ProtocolV2 WebUSB',
@@ -818,93 +825,9 @@ export default class WebUsbTransport {
818
825
  path: string,
819
826
  name: string,
820
827
  data: Record<string, unknown>,
821
- options?: TransportCallOptions,
822
- resetOnError = true
828
+ options?: TransportCallOptions
823
829
  ) {
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;
830
+ return this.callProtocolV2Usb(path, name, data, options);
908
831
  }
909
832
 
910
833
  /**
@@ -959,19 +882,63 @@ export default class WebUsbTransport {
959
882
  * Release device
960
883
  */
961
884
  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();
885
+ await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
886
+ await this.closeOpenDevice(path);
967
887
  this.deviceProtocol.delete(path);
968
888
  this.deviceProtocolHints.delete(path);
969
- this.protocolV2Assemblers.get(path)?.reset();
970
- this.protocolV2Assemblers.delete(path);
971
- this.protocolV2Sessions.delete(path);
972
889
  this.deviceEndpoints.delete(path);
973
890
  }
974
891
 
892
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
893
+ if (!this.messages || !this.messagesV2) {
894
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
895
+ }
896
+ return {
897
+ protocolV1: this.messages,
898
+ protocolV2: this.messagesV2,
899
+ };
900
+ }
901
+
902
+ protected getProtocolV2UsbLogger() {
903
+ return this.Log;
904
+ }
905
+
906
+ protected async writeProtocolV2UsbPacket(
907
+ path: string,
908
+ frame: Uint8Array,
909
+ _context: ProtocolV2CallContext
910
+ ): Promise<void> {
911
+ await this.transferOutOnce(path, frame);
912
+ }
913
+
914
+ protected async readProtocolV2UsbPacket(
915
+ path: string,
916
+ _context: ProtocolV2CallContext
917
+ ): Promise<Uint8Array> {
918
+ const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
919
+ return new Uint8Array(
920
+ this.toArrayBuffer(
921
+ dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
922
+ )
923
+ );
924
+ }
925
+
926
+ protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
927
+ await this.closeOpenDevice(path);
928
+ }
929
+
930
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
931
+ this.deviceProtocol.delete(path);
932
+ this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
933
+ }
934
+
935
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
936
+ return new ProtocolV2LinkError(
937
+ 'response-timeout',
938
+ `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
939
+ );
940
+ }
941
+
975
942
  /**
976
943
  * Expose the detected protocol type for a given device path.
977
944
  * Used by upper layers (e.g. TransportManager) to select the correct schema.