@onekeyfe/hd-transport-web-device 1.2.0-alpha.2 → 1.2.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/webusb.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-undef */
2
2
  import transport, {
3
- LogBlockCommand,
4
3
  PROTOCOL_V1_CHUNK_PAYLOAD_SIZE,
5
4
  PROTOCOL_V1_MESSAGE_HEADER_SIZE,
6
5
  PROTOCOL_V1_REPORT_ID,
@@ -8,12 +7,21 @@ import transport, {
8
7
  PROTOCOL_V2_CHANNEL_USB,
9
8
  PROTOCOL_V2_FRAME_MAX_BYTES,
10
9
  ProtocolV2FrameAssembler,
10
+ ProtocolV2SequenceCursor,
11
11
  ProtocolV2Session,
12
12
  probeProtocolV2 as probeProtocolV2Helper,
13
13
  } from '@onekeyfe/hd-transport';
14
- import { ERRORS, HardwareErrorCode, ONEKEY_WEBUSB_FILTER, wait } from '@onekeyfe/hd-shared';
14
+ import {
15
+ ERRORS,
16
+ HardwareErrorCode,
17
+ ONEKEY_WEBUSB_FILTER,
18
+ isKnownTrezorWebUsbDevice,
19
+ wait,
20
+ } from '@onekeyfe/hd-shared';
15
21
  import ByteBuffer from 'bytebuffer';
16
22
 
23
+ import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
24
+
17
25
  import type {
18
26
  AcquireInput,
19
27
  OneKeyDeviceInfoBase,
@@ -33,22 +41,6 @@ const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
33
41
  const PACKET_IO_MAX_RETRIES = 3;
34
42
  const PACKET_IO_RETRY_DELAY = 300;
35
43
  const PROTOCOL_PROBE_TIMEOUT = 1000;
36
- const WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
37
-
38
- function shouldSuppressWebUsbCallLog(name: string) {
39
- const normalized = name.replace(/[_\s-]/g, '');
40
- return WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
41
- }
42
-
43
- function isLogBlockCommand(name: string) {
44
- return (LogBlockCommand as Set<string> | undefined)?.has?.(name) ?? false;
45
- }
46
-
47
- function shouldBlockWebUsbCallDataLog(name: string) {
48
- const normalized = name.replace(/[_\s-]/g, '');
49
- return isLogBlockCommand(name) || WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
50
- }
51
-
52
44
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
53
45
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
54
46
  }
@@ -84,22 +76,24 @@ export default class WebUsbTransport {
84
76
 
85
77
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
86
78
 
87
- /** 按设备缓存 Protocol V2 frame assembler,保留同一次读取里多出来的后续 frame。 */
79
+ /** Per-device Protocol V2 assembler that retains extra frames from one read. */
88
80
  private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
89
81
 
90
- /** 按设备缓存 Protocol V2 session,保持 seq 与设备会话一致递增。 */
82
+ /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
91
83
  private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
92
84
 
93
- /** 当前 Protocol V2 调用的读取超时,由缓存 session readFrame 闭包读取。 */
85
+ /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
86
+ private protocolV2Sequences: Map<string, ProtocolV2SequenceCursor> = new Map();
87
+
88
+ /** Read timeout for the current Protocol V2 call, consumed by cached readFrame. */
94
89
  private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
95
90
 
96
91
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
97
92
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
98
93
 
99
94
  /**
100
- * 早期 Pro2 工程板 USB descriptor 没有烧录 serial number
101
- * 为这类设备生成会话内稳定的 mock path(同一 USBDevice 实例复用同一 path),
102
- * 避免设备因为空 serial 被发现流程整体丢弃。重新插拔后实例变化,path 会重新生成。
95
+ * Early Pro2 boards have no USB serial number. Assign a session-stable mock path per
96
+ * USBDevice instance so discovery retains them; reconnecting creates a new instance/path.
103
97
  */
104
98
  private mockSerialPaths: WeakMap<USBDevice, string> = new WeakMap();
105
99
 
@@ -159,7 +153,6 @@ export default class WebUsbTransport {
159
153
  this.messagesV2 = parseConfigure(signedData);
160
154
  this.protocolV2Sessions.clear();
161
155
  this.protocolV2ReadTimeouts.clear();
162
- this.Log?.debug('[WebUsbTransport] Protocol V2 schema configured');
163
156
  }
164
157
 
165
158
  /**
@@ -192,8 +185,7 @@ export default class WebUsbTransport {
192
185
  }
193
186
 
194
187
  /**
195
- * 设备 path:正常设备直接用 USB serial number;
196
- * 空 serial(早期工程板)回退到会话内稳定的 mock path。
188
+ * Use the USB serial as the device path, falling back to a session-stable mock path.
197
189
  */
198
190
  private getDevicePath(device: USBDevice): string {
199
191
  if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
@@ -218,11 +210,13 @@ export default class WebUsbTransport {
218
210
  if (!this.usb) return [];
219
211
 
220
212
  const devices = await this.usb.getDevices();
221
- const onekeyDevices = devices.filter(dev =>
222
- ONEKEY_WEBUSB_FILTER.some(
223
- desc => dev.vendorId === desc.vendorId && dev.productId === desc.productId
224
- )
225
- );
213
+ const onekeyDevices = devices.filter(dev => {
214
+ const isOneKey = ONEKEY_WEBUSB_FILTER.some(
215
+ (desc: { vendorId: number; productId: number }) =>
216
+ dev.vendorId === desc.vendorId && dev.productId === desc.productId
217
+ );
218
+ return isOneKey && !isKnownTrezorWebUsbDevice(dev);
219
+ });
226
220
 
227
221
  this.deviceList = onekeyDevices.map(device => {
228
222
  const path = this.getDevicePath(device);
@@ -238,14 +232,6 @@ export default class WebUsbTransport {
238
232
  };
239
233
  });
240
234
 
241
- // Debug: log all discovered devices. Protocol is detected after acquire via wire probe.
242
- for (const dev of onekeyDevices) {
243
- this.Log.debug(
244
- `[WebUSB] Device: name="${dev.productName}" serial="${dev.serialNumber}" ` +
245
- `VID=0x${dev.vendorId.toString(16)} PID=0x${dev.productId.toString(16)}`
246
- );
247
- }
248
-
249
235
  return this.deviceList;
250
236
  }
251
237
 
@@ -300,23 +286,21 @@ export default class WebUsbTransport {
300
286
  if (expectedProtocol === 'V1') {
301
287
  if (await this.probeProtocolV1(path)) {
302
288
  this.deviceProtocol.set(path, 'V1');
303
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
304
289
  return 'V1';
305
290
  }
291
+ await this.resetConnectionAfterProbe(path);
306
292
  throw this.createProtocolMismatchError(expectedProtocol);
307
293
  }
308
294
 
309
295
  if (expectedProtocol === 'V2') {
310
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
311
- // 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
296
+ // Skip probing when the caller explicitly confirms V2, such as reconnect after a
297
+ // firmware reboot where expectedProtocol carries the previously probed result.
312
298
  this.deviceProtocol.set(path, 'V2');
313
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
314
299
  return 'V2';
315
300
  }
316
301
 
317
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
318
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1
319
- // 不能作为最终结论。
302
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
303
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
320
304
  const probeOrder: ProtocolType[] =
321
305
  protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
322
306
 
@@ -325,9 +309,14 @@ export default class WebUsbTransport {
325
309
  protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
326
310
  if (detected) {
327
311
  this.deviceProtocol.set(path, protocol);
328
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
329
312
  return protocol;
330
313
  }
314
+ if (protocol === 'V1') {
315
+ // A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
316
+ // reopening the device guarantees the next protocol probe cannot consume a
317
+ // late V1 response. V2 timeout recovery is owned by callProtocolV2.
318
+ await this.resetConnectionAfterProbe(path);
319
+ }
331
320
  }
332
321
 
333
322
  this.deviceProtocol.delete(path);
@@ -414,13 +403,6 @@ export default class WebUsbTransport {
414
403
  */
415
404
  async connectToDevice(path: string, first: boolean) {
416
405
  let device: USBDevice = await this.findDevice(path);
417
- this.Log.debug(
418
- '[WebUsbTransport] connecting to device:',
419
- device.productName,
420
- 'PID:',
421
- device.productId
422
- );
423
-
424
406
  if (!device.opened) {
425
407
  await device.open();
426
408
  }
@@ -571,16 +553,7 @@ export default class WebUsbTransport {
571
553
  let lastError: unknown;
572
554
  for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
573
555
  try {
574
- const device = await this.findDevice(path);
575
- if (!device.opened) {
576
- await this.connect(path, false);
577
- }
578
- const endpoints = this.deviceEndpoints.get(path);
579
- const endpointOut = endpoints?.endpointOut ?? this.endpointId;
580
- const transferBuffer = this.toArrayBuffer(
581
- packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
582
- );
583
- await device.transferOut(endpointOut, transferBuffer);
556
+ await this.transferOutOnce(path, packet);
584
557
  return;
585
558
  } catch (error) {
586
559
  lastError = error;
@@ -603,6 +576,19 @@ export default class WebUsbTransport {
603
576
  throw lastError;
604
577
  }
605
578
 
579
+ private async transferOutOnce(path: string, packet: Uint8Array) {
580
+ const device = await this.findDevice(path);
581
+ if (!device.opened) {
582
+ await this.connect(path, false);
583
+ }
584
+ const endpoints = this.deviceEndpoints.get(path);
585
+ const endpointOut = endpoints?.endpointOut ?? this.endpointId;
586
+ const transferBuffer = this.toArrayBuffer(
587
+ packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
588
+ );
589
+ await device.transferOut(endpointOut, transferBuffer);
590
+ }
591
+
606
592
  private async transferInWithRetry(
607
593
  path: string,
608
594
  length: number,
@@ -672,13 +658,12 @@ export default class WebUsbTransport {
672
658
  }
673
659
 
674
660
  private async withProtocolReadTimeout<T>(
675
- path: string,
661
+ _path: string,
676
662
  promise: Promise<T>,
677
663
  timeoutMs: number,
678
664
  protocol: ProtocolType,
679
665
  onTimeout?: () => void
680
666
  ): Promise<T> {
681
- void path;
682
667
  let timer: ReturnType<typeof setTimeout> | undefined;
683
668
  let timedOut = false;
684
669
  const waitForeverAfterTimeout = () => new Promise<never>(() => {});
@@ -695,7 +680,7 @@ export default class WebUsbTransport {
695
680
  return await Promise.race([
696
681
  guardedPromise,
697
682
  new Promise<never>((_, reject) => {
698
- timer = setTimeout(async () => {
683
+ timer = setTimeout(() => {
699
684
  timedOut = true;
700
685
  onTimeout?.();
701
686
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
@@ -715,8 +700,7 @@ export default class WebUsbTransport {
715
700
  try {
716
701
  await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
717
702
  return true;
718
- } catch (error) {
719
- this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
703
+ } catch (_error) {
720
704
  return false;
721
705
  }
722
706
  }
@@ -731,7 +715,6 @@ export default class WebUsbTransport {
731
715
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
732
716
  logger: this.Log,
733
717
  logPrefix: 'ProtocolV2 WebUSB',
734
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
735
718
  });
736
719
  }
737
720
 
@@ -761,12 +744,8 @@ export default class WebUsbTransport {
761
744
  );
762
745
  }
763
746
 
764
- if (shouldSuppressWebUsbCallLog(name)) {
765
- // 高频文件写入不要逐包发 debug 事件,否则浏览器侧会被日志处理拖慢。
766
- } else if (shouldBlockWebUsbCallDataLog(name)) {
767
- this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
768
- } else {
769
- this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
747
+ if (!shouldSuppressHighVolumeCallLog(name)) {
748
+ this.Log.debug('transport call', createTransportCallLog(name, protocol));
770
749
  }
771
750
 
772
751
  if (protocol === 'V2') {
@@ -829,13 +808,19 @@ export default class WebUsbTransport {
829
808
 
830
809
  let session = this.protocolV2Sessions.get(path);
831
810
  if (!session) {
811
+ let sequenceCursor = this.protocolV2Sequences.get(path);
812
+ if (!sequenceCursor) {
813
+ sequenceCursor = new ProtocolV2SequenceCursor();
814
+ this.protocolV2Sequences.set(path, sequenceCursor);
815
+ }
832
816
  session = new ProtocolV2Session({
833
817
  schemas: {
834
818
  protocolV1: protocolV1Messages,
835
819
  protocolV2: this.messagesV2,
836
820
  },
837
821
  router: PROTOCOL_V2_CHANNEL_USB,
838
- writeFrame: (frame: Uint8Array) => this.transferOutWithRetry(path, frame),
822
+ sequenceCursor,
823
+ writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
839
824
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
840
825
  logger: this.Log,
841
826
  logPrefix: 'ProtocolV2 WebUSB',
@@ -849,6 +834,17 @@ export default class WebUsbTransport {
849
834
  this.protocolV2Assemblers.get(path)?.reset();
850
835
  try {
851
836
  return await session.call(name, data, options);
837
+ } catch (error) {
838
+ const message =
839
+ error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
840
+ if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
841
+ try {
842
+ await this.resetConnectionAfterProbe(path);
843
+ } catch (resetError) {
844
+ this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
845
+ }
846
+ }
847
+ throw error;
852
848
  } finally {
853
849
  this.protocolV2ReadTimeouts.delete(path);
854
850
  }
@@ -956,6 +952,8 @@ export default class WebUsbTransport {
956
952
  this.deviceProtocolHints.delete(path);
957
953
  this.protocolV2Assemblers.get(path)?.reset();
958
954
  this.protocolV2Assemblers.delete(path);
955
+ this.protocolV2Sessions.delete(path);
956
+ this.protocolV2ReadTimeouts.delete(path);
959
957
  this.deviceEndpoints.delete(path);
960
958
  }
961
959