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

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.
@@ -141,7 +141,6 @@ describe('ElectronBleTransport protocol detection', () => {
141
141
  { message: 'ok' },
142
142
  { router: PROTOCOL_V2_CHANNEL_BLE_UART }
143
143
  );
144
-
145
144
  nobleBle.onNotification.mockImplementation(handler => {
146
145
  notificationHandler = handler;
147
146
  return jest.fn();
@@ -220,19 +219,21 @@ describe('ElectronBleTransport protocol detection', () => {
220
219
  const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
221
220
  const nobleBle = createNobleBle(device);
222
221
  let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
223
- const probeResponse = ProtocolV2.encodeFrame(
224
- schemas,
225
- 'Success',
226
- { message: 'ok' },
227
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
228
- );
229
222
 
230
223
  nobleBle.onNotification.mockImplementation(handler => {
231
224
  notificationHandler = handler;
232
225
  return jest.fn();
233
226
  });
227
+ let responseSeq = 0;
234
228
  nobleBle.write.mockImplementation(() => {
235
- setTimeout(() => notificationHandler?.(device.id, bytesToHex(probeResponse)), 0);
229
+ responseSeq += 1;
230
+ const response = ProtocolV2.encodeFrame(
231
+ schemas,
232
+ 'Success',
233
+ { message: 'ok' },
234
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
235
+ );
236
+ setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
236
237
  return Promise.resolve();
237
238
  });
238
239
  const transport = configureTransport(nobleBle);
@@ -304,19 +305,20 @@ describe('ElectronBleTransport protocol detection', () => {
304
305
  const device = { id: 'repeated-acquire-pro2-id', name: 'OneKey Pro 2' };
305
306
  const nobleBle = createNobleBle(device);
306
307
  let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
307
- const response = ProtocolV2.encodeFrame(
308
- schemas,
309
- 'Success',
310
- { message: 'ok' },
311
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
312
- );
313
-
314
308
  nobleBle.onNotification.mockImplementation(handler => {
315
309
  notificationHandler = handler;
316
310
  return jest.fn();
317
311
  });
312
+ let responseSeq = 0;
318
313
  nobleBle.write.mockImplementation(() => {
319
- setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
314
+ responseSeq += 1;
315
+ const sequencedResponse = ProtocolV2.encodeFrame(
316
+ schemas,
317
+ 'Success',
318
+ { message: 'ok' },
319
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
320
+ );
321
+ setTimeout(() => notificationHandler?.(device.id, bytesToHex(sequencedResponse)), 0);
320
322
  return Promise.resolve();
321
323
  });
322
324
  const transport = configureTransport(nobleBle);
@@ -334,7 +336,7 @@ describe('ElectronBleTransport protocol detection', () => {
334
336
  const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
335
337
  Number.parseInt(hex.slice(12, 14), 16)
336
338
  );
337
- expect(sentSeqs).toEqual([1, 2]);
339
+ expect(sentSeqs).toEqual([1, 2, 3]);
338
340
  } finally {
339
341
  await transport.release(device.id);
340
342
  }
@@ -1,4 +1,8 @@
1
- import transport, { ProtocolV2FrameAssembler } from '@onekeyfe/hd-transport';
1
+ import transport, {
2
+ ProtocolV2,
3
+ ProtocolV2FrameAssembler,
4
+ ProtocolV2LinkError,
5
+ } from '@onekeyfe/hd-transport';
2
6
 
3
7
  import WebUsbTransport from '../src/webusb';
4
8
 
@@ -39,6 +43,38 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
39
43
  expect(webusb.deviceProtocol.get(path)).toBe('V2');
40
44
  });
41
45
 
46
+ test('retries an expected Protocol V2 probe once after resetting the connection', async () => {
47
+ const webusb = new WebUsbTransport() as any;
48
+ const path = 'pro2-webusb';
49
+ webusb.probeProtocolV1 = jest.fn();
50
+ webusb.probeProtocolV2 = jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true);
51
+ webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
52
+
53
+ await expect(webusb.detectProtocol(path, 'V2')).resolves.toBe('V2');
54
+
55
+ expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
56
+ expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
57
+ expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(1);
58
+ expect(webusb.deviceProtocol.get(path)).toBe('V2');
59
+ });
60
+
61
+ test('reports a Protocol V2 probe timeout only after the bounded retry is exhausted', async () => {
62
+ const webusb = new WebUsbTransport() as any;
63
+ const path = 'pro2-webusb';
64
+ webusb.probeProtocolV1 = jest.fn();
65
+ webusb.probeProtocolV2 = jest.fn().mockResolvedValue(false);
66
+ webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
67
+
68
+ await expect(webusb.detectProtocol(path, 'V2')).rejects.toThrow(
69
+ 'Protocol V2 probe timeout after 2 attempts'
70
+ );
71
+
72
+ expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
73
+ expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
74
+ expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(2);
75
+ expect(webusb.deviceProtocol.has(path)).toBe(false);
76
+ });
77
+
42
78
  test('invalidates and resets the cached connection before another call can start', async () => {
43
79
  const webusb = new WebUsbTransport() as any;
44
80
  const path = 'pro2-webusb';
@@ -49,7 +85,6 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
49
85
  webusb.receiveProtocolV2Frame = jest.fn(() => new Promise<void>(() => {}));
50
86
  webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
51
87
  webusb.protocolV2Sessions.delete(path);
52
- webusb.protocolV2ReadTimeouts.delete(path);
53
88
  webusb.protocolV2Assemblers.get(path)?.reset();
54
89
  });
55
90
 
@@ -60,4 +95,102 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
60
95
  expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
61
96
  expect(webusb.protocolV2Sessions.has(path)).toBe(false);
62
97
  });
98
+
99
+ test.each(['router', 'packet-source', 'ack-sequence', 'response-sequence', 'frame'] as const)(
100
+ 'invalidates cached state for typed Protocol V2 %s errors',
101
+ async code => {
102
+ const webusb = new WebUsbTransport() as any;
103
+ const path = 'pro2-webusb';
104
+ webusb.messages = transport.parseConfigure(schema);
105
+ webusb.messagesV2 = transport.parseConfigure(schema);
106
+ webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
107
+ webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
108
+ const recoveredResponse = ProtocolV2.encodeFrame(
109
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
110
+ 'Success',
111
+ { message: 'recovered' },
112
+ { seq: 1 }
113
+ );
114
+ webusb.receiveProtocolV2Frame = jest
115
+ .fn()
116
+ .mockRejectedValueOnce(
117
+ new ProtocolV2LinkError(code, `Protocol V2 ${code} validation failed`)
118
+ )
119
+ .mockResolvedValue(recoveredResponse);
120
+ webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
121
+ webusb.protocolV2Sessions.delete(path);
122
+ webusb.protocolV2Assemblers.get(path)?.reset();
123
+ });
124
+
125
+ await expect(webusb.callProtocolV2(path, 'Ping', { message: 'mismatch' })).rejects.toThrow(
126
+ `${code} validation failed`
127
+ );
128
+
129
+ expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
130
+ expect(webusb.protocolV2Sessions.has(path)).toBe(false);
131
+
132
+ await expect(
133
+ webusb.callProtocolV2(path, 'Ping', { message: 'after-reset' })
134
+ ).resolves.toMatchObject({
135
+ type: 'Success',
136
+ message: { message: 'recovered' },
137
+ });
138
+ }
139
+ );
140
+
141
+ test('does not discard buffered Protocol V2 frames before each call', async () => {
142
+ const webusb = new WebUsbTransport() as any;
143
+ const path = 'pro2-webusb';
144
+ const assembler = new ProtocolV2FrameAssembler();
145
+ const reset = jest.spyOn(assembler, 'reset');
146
+ let responseSequence = 0;
147
+ webusb.messages = transport.parseConfigure(schema);
148
+ 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
+ });
161
+
162
+ await webusb.callProtocolV2(path, 'Ping', { message: 'first' });
163
+ await webusb.callProtocolV2(path, 'Ping', { message: 'second' });
164
+
165
+ expect(reset).not.toHaveBeenCalled();
166
+ });
167
+
168
+ test('keeps queued Protocol V2 read timeouts scoped to each call', async () => {
169
+ const webusb = new WebUsbTransport() as any;
170
+ const path = 'pro2-webusb';
171
+ let responseSequence = 0;
172
+ webusb.messages = transport.parseConfigure(schema);
173
+ webusb.messagesV2 = transport.parseConfigure(schema);
174
+ webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
175
+ webusb.receiveProtocolV2Frame = jest.fn().mockImplementation(() => {
176
+ responseSequence += 1;
177
+ return Promise.resolve(
178
+ ProtocolV2.encodeFrame(
179
+ { protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
180
+ 'Success',
181
+ { message: 'ok' },
182
+ { seq: responseSequence }
183
+ )
184
+ );
185
+ });
186
+
187
+ await Promise.all([
188
+ webusb.callProtocolV2(path, 'Ping', { message: 'long' }, { timeoutMs: 1_000 }),
189
+ webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
190
+ ]);
191
+
192
+ expect(
193
+ webusb.receiveProtocolV2Frame.mock.calls.map(([, timeoutMs]: unknown[]) => timeoutMs)
194
+ ).toEqual([1_000, 25]);
195
+ });
63
196
  });
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC;AAqCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,SAAS,CAAsE;IAEvF,OAAO,CAAC,YAAY,CAAoD;IAExE,OAAO,CAAC,aAAa,CAAwC;IAE7D,OAAO,CAAC,eAAe,CAAgD;IAEvE,OAAO,CAAC,eAAe,CAmBpB;IAEH,OAAO,CAAC,oBAAoB,CAAsC;IAElE,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,kBAAkB,CAAkC;IAE5D,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IA0B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAO7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IA0F9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAgBxB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA8C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAqCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,SAAS;IASvB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAgB9B,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAuEd,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC;AAoCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,SAAS,CAAsE;IAEvF,OAAO,CAAC,YAAY,CAAoD;IAExE,OAAO,CAAC,aAAa,CAAwC;IAE7D,OAAO,CAAC,eAAe,CAAgD;IAEvE,OAAO,CAAC,eAAe,CAmBpB;IAEH,OAAO,CAAC,oBAAoB,CAAsC;IAElE,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,kBAAkB,CAAkC;IAE5D,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IA0B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAO7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IA0F9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAgBxB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAqCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,SAAS;IASvB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAgB9B,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAuEd,cAAc;IA0B5B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
package/dist/index.d.ts CHANGED
@@ -25,8 +25,6 @@ declare class WebUsbTransport {
25
25
  private protocolV2Sessions;
26
26
  /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
27
27
  private protocolV2Sequences;
28
- /** Read timeout for the current Protocol V2 call, consumed by cached readFrame. */
29
- private protocolV2ReadTimeouts;
30
28
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
31
29
  private deviceEndpoints;
32
30
  /**
@@ -90,6 +88,7 @@ declare class WebUsbTransport {
90
88
  * fall back to a Protocol V2 Ping probe.
91
89
  */
92
90
  private createProtocolMismatchError;
91
+ private createProtocolProbeTimeoutError;
93
92
  private createProtocolDetectionError;
94
93
  private detectProtocol;
95
94
  /**
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
60
60
  const PACKET_IO_MAX_RETRIES = 3;
61
61
  const PACKET_IO_RETRY_DELAY = 300;
62
62
  const PROTOCOL_PROBE_TIMEOUT = 1000;
63
+ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
63
64
  function inferProtocolHintFromDeviceName$1(name) {
64
65
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
65
66
  }
@@ -70,7 +71,6 @@ class WebUsbTransport {
70
71
  this.protocolV2Assemblers = new Map();
71
72
  this.protocolV2Sessions = new Map();
72
73
  this.protocolV2Sequences = new Map();
73
- this.protocolV2ReadTimeouts = new Map();
74
74
  this.deviceEndpoints = new Map();
75
75
  this.mockSerialPaths = new WeakMap();
76
76
  this.mockSerialCounter = 0;
@@ -98,7 +98,6 @@ class WebUsbTransport {
98
98
  configureProtocolV2(signedData) {
99
99
  this.messagesV2 = parseConfigure$1(signedData);
100
100
  this.protocolV2Sessions.clear();
101
- this.protocolV2ReadTimeouts.clear();
102
101
  }
103
102
  promptDeviceAccess() {
104
103
  return __awaiter(this, void 0, void 0, function* () {
@@ -185,10 +184,14 @@ class WebUsbTransport {
185
184
  createProtocolMismatchError(expected) {
186
185
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
187
186
  }
187
+ createProtocolProbeTimeoutError(expected, attempts) {
188
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Protocol ${expected} probe timeout after ${attempts} attempts`);
189
+ }
188
190
  createProtocolDetectionError() {
189
191
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
190
192
  }
191
193
  detectProtocol(path, expectedProtocol, protocolHint) {
194
+ var _a;
192
195
  return __awaiter(this, void 0, void 0, function* () {
193
196
  if (expectedProtocol === 'V1') {
194
197
  if (yield this.probeProtocolV1(path)) {
@@ -199,8 +202,18 @@ class WebUsbTransport {
199
202
  throw this.createProtocolMismatchError(expectedProtocol);
200
203
  }
201
204
  if (expectedProtocol === 'V2') {
202
- this.deviceProtocol.set(path, 'V2');
203
- return 'V2';
205
+ for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
206
+ if (yield this.probeProtocolV2(path)) {
207
+ this.deviceProtocol.set(path, 'V2');
208
+ return 'V2';
209
+ }
210
+ yield this.resetConnectionAfterProbe(path);
211
+ if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
212
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 probe timed out, retrying ${attempt + 1}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`);
213
+ }
214
+ }
215
+ this.deviceProtocol.delete(path);
216
+ throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
204
217
  }
205
218
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
206
219
  for (const protocol of probeOrder) {
@@ -209,9 +222,7 @@ class WebUsbTransport {
209
222
  this.deviceProtocol.set(path, protocol);
210
223
  return protocol;
211
224
  }
212
- if (protocol === 'V1') {
213
- yield this.resetConnectionAfterProbe(path);
214
- }
225
+ yield this.resetConnectionAfterProbe(path);
215
226
  }
216
227
  this.deviceProtocol.delete(path);
217
228
  throw this.createProtocolDetectionError();
@@ -495,7 +506,6 @@ class WebUsbTransport {
495
506
  return __awaiter(this, void 0, void 0, function* () {
496
507
  (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
497
508
  this.protocolV2Sessions.delete(path);
498
- this.protocolV2ReadTimeouts.delete(path);
499
509
  try {
500
510
  const device = yield this.findDevice(path);
501
511
  if (device.opened) {
@@ -517,7 +527,7 @@ class WebUsbTransport {
517
527
  yield this.connect(path, false);
518
528
  });
519
529
  }
520
- withProtocolReadTimeout(_path, promise, timeoutMs, protocol, onTimeout) {
530
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
521
531
  return __awaiter(this, void 0, void 0, function* () {
522
532
  let timer;
523
533
  let timedOut = false;
@@ -532,11 +542,19 @@ class WebUsbTransport {
532
542
  return yield Promise.race([
533
543
  guardedPromise,
534
544
  new Promise((_, reject) => {
535
- timer = setTimeout(() => {
545
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
536
546
  timedOut = true;
537
547
  onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
548
+ if (protocol === 'V1') {
549
+ try {
550
+ yield this.resetConnectionAfterProbe(path);
551
+ }
552
+ catch (error) {
553
+ this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
554
+ }
555
+ }
538
556
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
539
- }, timeoutMs);
557
+ }), timeoutMs);
540
558
  }),
541
559
  ]);
542
560
  }
@@ -566,7 +584,7 @@ class WebUsbTransport {
566
584
  return false;
567
585
  }
568
586
  return transport.probeProtocolV2({
569
- call: (name, data, options) => this.callProtocolV2(path, name, data, options),
587
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
570
588
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
571
589
  logger: this.Log,
572
590
  logPrefix: 'ProtocolV2 WebUSB',
@@ -616,8 +634,7 @@ class WebUsbTransport {
616
634
  return check$1.call(jsonData);
617
635
  });
618
636
  }
619
- callProtocolV2(path, name, data, options) {
620
- var _a;
637
+ callProtocolV2(path, name, data, options, resetOnError = true) {
621
638
  return __awaiter(this, void 0, void 0, function* () {
622
639
  const protocolV1Messages = this.messages;
623
640
  if (!this.messagesV2) {
@@ -641,33 +658,27 @@ class WebUsbTransport {
641
658
  router: transport.PROTOCOL_V2_CHANNEL_USB,
642
659
  sequenceCursor,
643
660
  writeFrame: (frame) => this.transferOutOnce(path, frame),
644
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
661
+ readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
645
662
  logger: this.Log,
646
663
  logPrefix: 'ProtocolV2 WebUSB',
647
- createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
664
+ createTimeoutError: (messageName, timeoutMs) => new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
648
665
  });
649
666
  this.protocolV2Sessions.set(path, session);
650
667
  }
651
- this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
652
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
653
668
  try {
654
669
  return yield session.call(name, data, options);
655
670
  }
656
671
  catch (error) {
657
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
658
- if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
672
+ if (resetOnError && (transport.isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
659
673
  try {
660
674
  yield this.resetConnectionAfterProbe(path);
661
675
  }
662
676
  catch (resetError) {
663
- this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
677
+ this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
664
678
  }
665
679
  }
666
680
  throw error;
667
681
  }
668
- finally {
669
- this.protocolV2ReadTimeouts.delete(path);
670
- }
671
682
  });
672
683
  }
673
684
  receiveProtocolV2Frame(path, timeoutMs) {
@@ -688,12 +699,7 @@ class WebUsbTransport {
688
699
  })
689
700
  : yield transferIn;
690
701
  const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
691
- try {
692
- frame = assembler.push(bytes);
693
- }
694
- catch (error) {
695
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
696
- }
702
+ frame = assembler.push(bytes);
697
703
  }
698
704
  return frame;
699
705
  });
@@ -748,7 +754,6 @@ class WebUsbTransport {
748
754
  (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
749
755
  this.protocolV2Assemblers.delete(path);
750
756
  this.protocolV2Sessions.delete(path);
751
- this.protocolV2ReadTimeouts.delete(path);
752
757
  this.deviceEndpoints.delete(path);
753
758
  });
754
759
  }
@@ -764,7 +769,6 @@ function inferProtocolHintFromDeviceName(name) {
764
769
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
765
770
  const BLE_PACKET_SIZE = 192;
766
771
  const BLE_WRITE_DELAY_MS = 5;
767
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
768
772
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
769
773
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
770
774
  class ElectronBleTransport {
@@ -1023,9 +1027,12 @@ class ElectronBleTransport {
1023
1027
  throw this.createProtocolMismatchError(expectedProtocol);
1024
1028
  }
1025
1029
  if (expectedProtocol === 'V2') {
1026
- this.deviceProtocol.set(uuid, 'V2');
1027
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1028
- return 'V2';
1030
+ if (yield this.probeProtocolV2(uuid)) {
1031
+ this.deviceProtocol.set(uuid, 'V2');
1032
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1033
+ return 'V2';
1034
+ }
1035
+ throw this.createProtocolMismatchError(expectedProtocol);
1029
1036
  }
1030
1037
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1031
1038
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1361,17 +1368,17 @@ class ElectronBleTransport {
1361
1368
  });
1362
1369
  }
1363
1370
  callProtocolV2(uuid, name, data, options) {
1364
- var _a, _b;
1371
+ var _a;
1365
1372
  return __awaiter(this, void 0, void 0, function* () {
1366
1373
  if (!this._messages || !this._messagesV2) {
1367
1374
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1368
1375
  }
1369
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : BLE_RESPONSE_TIMEOUT_MS });
1376
+ const callOptions = options;
1370
1377
  try {
1371
1378
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1372
1379
  }
1373
1380
  catch (e) {
1374
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1381
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1375
1382
  throw e;
1376
1383
  }
1377
1384
  });
package/dist/webusb.d.ts CHANGED
@@ -14,7 +14,6 @@ export default class WebUsbTransport {
14
14
  private protocolV2Assemblers;
15
15
  private protocolV2Sessions;
16
16
  private protocolV2Sequences;
17
- private protocolV2ReadTimeouts;
18
17
  private deviceEndpoints;
19
18
  private mockSerialPaths;
20
19
  private mockSerialCounter;
@@ -36,6 +35,7 @@ export default class WebUsbTransport {
36
35
  getConnectedDevices(): Promise<DeviceInfo[]>;
37
36
  acquire(input: AcquireInput): Promise<string | undefined>;
38
37
  private createProtocolMismatchError;
38
+ private createProtocolProbeTimeoutError;
39
39
  private createProtocolDetectionError;
40
40
  private detectProtocol;
41
41
  findDevice(path: string): Promise<USBDevice>;
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAWN,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAqBhC,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,sBAAsB,CAA8C;IAG5E,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;IAW7B,kBAAkB;IAmBlB,SAAS;IAQf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAgCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IAgDtB,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;YAyBzB,uBAAuB;YAmCvB,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;YA6Dd,sBAAsB;IA4C9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAmB1B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.0-alpha.21",
3
+ "version": "1.2.0-alpha.23",
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.21",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.21"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.23",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.23"
25
25
  },
26
26
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.21",
27
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.23",
28
28
  "@types/w3c-web-usb": "^1.0.6",
29
29
  "@types/web-bluetooth": "^0.0.17"
30
30
  },
31
- "gitHead": "efe594367fb3b196a5126baee7e2aba3e94986cd"
31
+ "gitHead": "7cadb40ca6414053a488756f28a9f413164b235d"
32
32
  }
@@ -62,7 +62,6 @@ const toBleDescriptor = (
62
62
 
63
63
  const BLE_PACKET_SIZE = 192;
64
64
  const BLE_WRITE_DELAY_MS = 5;
65
- const BLE_RESPONSE_TIMEOUT_MS = 30_000;
66
65
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
67
66
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
68
67
 
@@ -379,11 +378,12 @@ export default class ElectronBleTransport {
379
378
  }
380
379
 
381
380
  if (expectedProtocol === 'V2') {
382
- // Skip probing when the caller explicitly confirms V2, such as reconnect after a
383
- // firmware reboot where expectedProtocol carries the previously probed result.
384
- this.deviceProtocol.set(uuid, 'V2');
385
- this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
386
- return 'V2';
381
+ if (await this.probeProtocolV2(uuid)) {
382
+ this.deviceProtocol.set(uuid, 'V2');
383
+ this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
384
+ return 'V2';
385
+ }
386
+ throw this.createProtocolMismatchError(expectedProtocol);
387
387
  }
388
388
 
389
389
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -761,10 +761,7 @@ export default class ElectronBleTransport {
761
761
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
762
762
  }
763
763
 
764
- const callOptions = {
765
- ...options,
766
- timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
767
- };
764
+ const callOptions = options;
768
765
 
769
766
  try {
770
767
  return await this.protocolV2Links.call(
package/src/webusb.ts CHANGED
@@ -7,8 +7,10 @@ import transport, {
7
7
  PROTOCOL_V2_CHANNEL_USB,
8
8
  PROTOCOL_V2_FRAME_MAX_BYTES,
9
9
  ProtocolV2FrameAssembler,
10
+ ProtocolV2LinkError,
10
11
  ProtocolV2SequenceCursor,
11
12
  ProtocolV2Session,
13
+ isProtocolV2LinkError,
12
14
  probeProtocolV2 as probeProtocolV2Helper,
13
15
  } from '@onekeyfe/hd-transport';
14
16
  import {
@@ -41,6 +43,7 @@ const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
41
43
  const PACKET_IO_MAX_RETRIES = 3;
42
44
  const PACKET_IO_RETRY_DELAY = 300;
43
45
  const PROTOCOL_PROBE_TIMEOUT = 1000;
46
+ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
44
47
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
45
48
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
46
49
  }
@@ -85,9 +88,6 @@ export default class WebUsbTransport {
85
88
  /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
86
89
  private protocolV2Sequences: Map<string, ProtocolV2SequenceCursor> = new Map();
87
90
 
88
- /** Read timeout for the current Protocol V2 call, consumed by cached readFrame. */
89
- private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
90
-
91
91
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
92
92
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
93
93
 
@@ -152,7 +152,6 @@ export default class WebUsbTransport {
152
152
  configureProtocolV2(signedData: any) {
153
153
  this.messagesV2 = parseConfigure(signedData);
154
154
  this.protocolV2Sessions.clear();
155
- this.protocolV2ReadTimeouts.clear();
156
155
  }
157
156
 
158
157
  /**
@@ -271,6 +270,13 @@ export default class WebUsbTransport {
271
270
  );
272
271
  }
273
272
 
273
+ private createProtocolProbeTimeoutError(expected: ProtocolType, attempts: number) {
274
+ return ERRORS.TypedError(
275
+ HardwareErrorCode.RuntimeError,
276
+ `Protocol ${expected} probe timeout after ${attempts} attempts`
277
+ );
278
+ }
279
+
274
280
  private createProtocolDetectionError() {
275
281
  return ERRORS.TypedError(
276
282
  HardwareErrorCode.RuntimeError,
@@ -293,10 +299,25 @@ export default class WebUsbTransport {
293
299
  }
294
300
 
295
301
  if (expectedProtocol === 'V2') {
296
- // Skip probing when the caller explicitly confirms V2, such as reconnect after a
297
- // firmware reboot where expectedProtocol carries the previously probed result.
298
- this.deviceProtocol.set(path, 'V2');
299
- return 'V2';
302
+ for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
303
+ if (await this.probeProtocolV2(path)) {
304
+ this.deviceProtocol.set(path, 'V2');
305
+ return 'V2';
306
+ }
307
+ await this.resetConnectionAfterProbe(path);
308
+ if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
309
+ this.Log?.debug(
310
+ `[WebUsbTransport] Protocol V2 probe timed out, retrying ${
311
+ attempt + 1
312
+ }/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
313
+ );
314
+ }
315
+ }
316
+ this.deviceProtocol.delete(path);
317
+ throw this.createProtocolProbeTimeoutError(
318
+ expectedProtocol,
319
+ EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS
320
+ );
300
321
  }
301
322
 
302
323
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -311,12 +332,10 @@ export default class WebUsbTransport {
311
332
  this.deviceProtocol.set(path, protocol);
312
333
  return protocol;
313
334
  }
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
- }
335
+ // A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
336
+ // reopening the device guarantees the next protocol probe cannot consume a
337
+ // late response from the previous protocol generation.
338
+ await this.resetConnectionAfterProbe(path);
320
339
  }
321
340
 
322
341
  this.deviceProtocol.delete(path);
@@ -635,7 +654,6 @@ export default class WebUsbTransport {
635
654
  private async resetConnectionAfterProbe(path: string) {
636
655
  this.protocolV2Assemblers.get(path)?.reset();
637
656
  this.protocolV2Sessions.delete(path);
638
- this.protocolV2ReadTimeouts.delete(path);
639
657
 
640
658
  try {
641
659
  const device = await this.findDevice(path);
@@ -658,7 +676,7 @@ export default class WebUsbTransport {
658
676
  }
659
677
 
660
678
  private async withProtocolReadTimeout<T>(
661
- _path: string,
679
+ path: string,
662
680
  promise: Promise<T>,
663
681
  timeoutMs: number,
664
682
  protocol: ProtocolType,
@@ -680,9 +698,16 @@ export default class WebUsbTransport {
680
698
  return await Promise.race([
681
699
  guardedPromise,
682
700
  new Promise<never>((_, reject) => {
683
- timer = setTimeout(() => {
701
+ timer = setTimeout(async () => {
684
702
  timedOut = true;
685
703
  onTimeout?.();
704
+ if (protocol === 'V1') {
705
+ try {
706
+ await this.resetConnectionAfterProbe(path);
707
+ } catch (error) {
708
+ this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
709
+ }
710
+ }
686
711
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
687
712
  }, timeoutMs);
688
713
  }),
@@ -711,7 +736,7 @@ export default class WebUsbTransport {
711
736
  }
712
737
 
713
738
  return probeProtocolV2Helper({
714
- call: (name, data, options) => this.callProtocolV2(path, name, data, options),
739
+ call: (name, data, options) => this.callProtocolV2(path, name, data, options, false),
715
740
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
716
741
  logger: this.Log,
717
742
  logPrefix: 'ProtocolV2 WebUSB',
@@ -793,7 +818,8 @@ export default class WebUsbTransport {
793
818
  path: string,
794
819
  name: string,
795
820
  data: Record<string, unknown>,
796
- options?: TransportCallOptions
821
+ options?: TransportCallOptions,
822
+ resetOnError = true
797
823
  ) {
798
824
  const protocolV1Messages = this.messages;
799
825
  if (!this.messagesV2) {
@@ -821,32 +847,29 @@ export default class WebUsbTransport {
821
847
  router: PROTOCOL_V2_CHANNEL_USB,
822
848
  sequenceCursor,
823
849
  writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
824
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
850
+ readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
825
851
  logger: this.Log,
826
852
  logPrefix: 'ProtocolV2 WebUSB',
827
853
  createTimeoutError: (messageName: string, timeoutMs: number) =>
828
- new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
854
+ new ProtocolV2LinkError(
855
+ 'response-timeout',
856
+ `Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`
857
+ ),
829
858
  });
830
859
  this.protocolV2Sessions.set(path, session);
831
860
  }
832
861
 
833
- this.protocolV2ReadTimeouts.set(path, options?.timeoutMs);
834
- this.protocolV2Assemblers.get(path)?.reset();
835
862
  try {
836
863
  return await session.call(name, data, options);
837
864
  } 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')) {
865
+ if (resetOnError && (isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
841
866
  try {
842
867
  await this.resetConnectionAfterProbe(path);
843
868
  } catch (resetError) {
844
- this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
869
+ this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
845
870
  }
846
871
  }
847
872
  throw error;
848
- } finally {
849
- this.protocolV2ReadTimeouts.delete(path);
850
873
  }
851
874
  }
852
875
 
@@ -879,14 +902,7 @@ export default class WebUsbTransport {
879
902
  dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
880
903
  )
881
904
  );
882
- try {
883
- frame = assembler.push(bytes);
884
- } catch (error) {
885
- throw ERRORS.TypedError(
886
- HardwareErrorCode.NetworkError,
887
- error instanceof Error ? error.message : String(error)
888
- );
889
- }
905
+ frame = assembler.push(bytes);
890
906
  }
891
907
  return frame;
892
908
  }
@@ -953,7 +969,6 @@ export default class WebUsbTransport {
953
969
  this.protocolV2Assemblers.get(path)?.reset();
954
970
  this.protocolV2Assemblers.delete(path);
955
971
  this.protocolV2Sessions.delete(path);
956
- this.protocolV2ReadTimeouts.delete(path);
957
972
  this.deviceEndpoints.delete(path);
958
973
  }
959
974