@onekeyfe/hd-transport-usb 1.2.0-alpha.18 → 1.2.0-alpha.181

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.
@@ -62,6 +62,7 @@ const createHarness = () => {
62
62
  const path = '6136';
63
63
  const responseQueue: Buffer[] = [];
64
64
  const sentSeqs: number[] = [];
65
+ let cancelledTransferCount = 0;
65
66
  let writeError: Error | undefined;
66
67
  let holdNextRead:
67
68
  | {
@@ -71,29 +72,84 @@ const createHarness = () => {
71
72
  }
72
73
  | undefined;
73
74
 
75
+ const performInTransfer = (callback: (error?: Error, data?: Buffer) => void) => {
76
+ if (epIn.timeout === 50) {
77
+ callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
78
+ return;
79
+ }
80
+ if (holdNextRead) {
81
+ const pending = holdNextRead;
82
+ holdNextRead = undefined;
83
+ pending.callback = callback;
84
+ pending.markStarted();
85
+ return;
86
+ }
87
+ const response = responseQueue.shift();
88
+ if (!response) {
89
+ callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
90
+ return;
91
+ }
92
+ callback(undefined, response);
93
+ };
94
+
74
95
  const epIn = {
75
96
  direction: 'in',
76
97
  address: 0x81,
77
98
  timeout: 30_000,
78
99
  transfer: jest.fn((_length: number, callback: (error?: Error, data?: Buffer) => void) => {
79
- if (epIn.timeout === 50) {
80
- callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
81
- return;
82
- }
83
- if (holdNextRead) {
84
- const pending = holdNextRead;
85
- holdNextRead = undefined;
86
- pending.callback = callback;
87
- pending.markStarted();
88
- return;
89
- }
90
- const response = responseQueue.shift();
91
- if (!response) {
92
- callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
93
- return;
94
- }
95
- callback(undefined, response);
100
+ performInTransfer(callback);
96
101
  }),
102
+ makeTransfer: jest.fn(
103
+ (
104
+ _timeout: number,
105
+ callback: (error: Error | undefined, data: Buffer, actualLength: number) => void
106
+ ) => {
107
+ let settled = false;
108
+ const finish = (error?: Error, data = Buffer.alloc(0)) => {
109
+ if (settled) return;
110
+ settled = true;
111
+ callback(error, data, data.length);
112
+ };
113
+ return {
114
+ submit: jest.fn((buffer: Buffer) => {
115
+ performInTransfer((error, data) => {
116
+ if (error) {
117
+ finish(error);
118
+ return;
119
+ }
120
+ data?.copy(buffer);
121
+ finish(undefined, buffer.subarray(0, data?.length ?? 0));
122
+ });
123
+ }),
124
+ cancel: jest.fn(() => {
125
+ cancelledTransferCount += 1;
126
+ finish(new Error('LIBUSB_TRANSFER_CANCELLED'));
127
+ }),
128
+ };
129
+ }
130
+ ),
131
+ };
132
+
133
+ const performOutTransfer = (data: Buffer, callback: (error?: Error) => void) => {
134
+ const seq = data[6];
135
+ sentSeqs.push(seq);
136
+ if (writeError) {
137
+ const error = writeError;
138
+ writeError = undefined;
139
+ callback(error);
140
+ return;
141
+ }
142
+ responseQueue.push(
143
+ Buffer.from(
144
+ ProtocolV2.encodeFrame(
145
+ schemas,
146
+ 'Success',
147
+ { message: 'ok' },
148
+ { router: PROTOCOL_V2_CHANNEL_USB, seq }
149
+ )
150
+ )
151
+ );
152
+ callback();
97
153
  };
98
154
 
99
155
  const epOut = {
@@ -101,33 +157,40 @@ const createHarness = () => {
101
157
  address: 0x01,
102
158
  timeout: 30_000,
103
159
  transfer: jest.fn((data: Buffer, callback: (error?: Error) => void) => {
104
- const seq = data[6];
105
- sentSeqs.push(seq);
106
- if (writeError) {
107
- const error = writeError;
108
- writeError = undefined;
109
- callback(error);
110
- return;
111
- }
112
- responseQueue.push(
113
- Buffer.from(
114
- ProtocolV2.encodeFrame(
115
- schemas,
116
- 'Success',
117
- { message: 'ok' },
118
- { router: PROTOCOL_V2_CHANNEL_USB, seq }
119
- )
120
- )
121
- );
122
- callback();
160
+ performOutTransfer(data, callback);
123
161
  }),
162
+ makeTransfer: jest.fn(
163
+ (
164
+ _timeout: number,
165
+ callback: (error: Error | undefined, data: Buffer, actualLength: number) => void
166
+ ) => {
167
+ let settled = false;
168
+ const finish = (error: Error | undefined, data: Buffer) => {
169
+ if (settled) return;
170
+ settled = true;
171
+ callback(error, data, data.length);
172
+ };
173
+ return {
174
+ submit: jest.fn((data: Buffer) => {
175
+ performOutTransfer(data, error => finish(error, data));
176
+ }),
177
+ cancel: jest.fn(() => {
178
+ cancelledTransferCount += 1;
179
+ finish(new Error('LIBUSB_TRANSFER_CANCELLED'), Buffer.alloc(0));
180
+ }),
181
+ };
182
+ }
183
+ ),
124
184
  };
125
185
 
126
186
  const iface = {
127
187
  descriptor: { bInterfaceClass: 0xff, bInterfaceNumber: 0 },
128
188
  endpoints: [epIn, epOut],
129
189
  claim: jest.fn(),
130
- release: jest.fn((callback: () => void) => callback()),
190
+ release: jest.fn((closeEndpointsOrCallback: boolean | (() => void), callback?: () => void) => {
191
+ if (typeof closeEndpointsOrCallback === 'function') closeEndpointsOrCallback();
192
+ else callback?.();
193
+ }),
131
194
  isKernelDriverActive: jest.fn(() => false),
132
195
  detachKernelDriver: jest.fn(),
133
196
  };
@@ -164,6 +227,7 @@ const createHarness = () => {
164
227
  epIn,
165
228
  epOut,
166
229
  sentSeqs,
230
+ getCancelledTransferCount: () => cancelledTransferCount,
167
231
  async acquire() {
168
232
  await transport.enumerate();
169
233
  await transport.acquire({ path, expectedProtocol: 'V2' });
@@ -189,19 +253,108 @@ const createHarness = () => {
189
253
  };
190
254
 
191
255
  describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
192
- test('trusts explicit Protocol V2 during bootloader reconnect without probing Ping', async () => {
256
+ test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
257
+ const transport = new NodeUsbTransport() as any;
258
+ const events: string[] = [];
259
+ transport.probeProtocolV1 = jest.fn().mockImplementation(() => {
260
+ events.push('probe-v1');
261
+ return Promise.resolve(false);
262
+ });
263
+ transport.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
264
+ events.push('reset');
265
+ return Promise.resolve();
266
+ });
267
+ transport.probeProtocolV2 = jest.fn().mockImplementation(() => {
268
+ events.push('probe-v2');
269
+ return Promise.resolve(true);
270
+ });
271
+
272
+ await expect(transport.detectProtocol('pro-usb', undefined, 'V1')).resolves.toBe('V2');
273
+
274
+ expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
275
+ expect(transport.getProtocolType('pro-usb')).toBe('V2');
276
+ });
277
+
278
+ test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
279
+ const transport = new NodeUsbTransport() as any;
280
+ transport.invalidateAllProtocolV2UsbLinks = jest.fn().mockResolvedValue(undefined);
281
+ const schemaSource = JSON.stringify(protocolV2Schema);
282
+
283
+ transport.configureProtocolV2(schemaSource);
284
+ transport.configureProtocolV2(schemaSource);
285
+
286
+ expect(transport.invalidateAllProtocolV2UsbLinks).not.toHaveBeenCalled();
287
+ });
288
+
289
+ test('does not retry a native transfer cancelled by probe cleanup', () => {
290
+ const { transport } = createHarness();
291
+
292
+ expect((transport as any).isRetryableError(new Error('LIBUSB_TRANSFER_CANCELLED'))).toBe(false);
293
+ });
294
+
295
+ test('cancels pending native transfers before closing a timed-out protocol probe', async () => {
296
+ const harness = createHarness();
297
+ const { transport, path } = harness;
298
+ await harness.acquire();
299
+ const cancelActiveTransfers = jest.spyOn(transport as any, 'cancelActiveTransfers');
300
+ const closeOpenDevice = jest.spyOn(transport as any, 'closeOpenDevice');
301
+
302
+ await (transport as any).resetConnectionAfterProbe(path);
303
+
304
+ expect(cancelActiveTransfers).toHaveBeenCalledWith(path);
305
+ expect(closeOpenDevice).toHaveBeenCalledWith(path);
306
+ expect(cancelActiveTransfers.mock.invocationCallOrder[0]).toBeLessThan(
307
+ closeOpenDevice.mock.invocationCallOrder[0]
308
+ );
309
+ });
310
+
311
+ test('releases the USB interface when protocol detection rejects acquire', async () => {
312
+ const harness = createHarness();
313
+ const { transport, path, device, iface } = harness;
314
+ await transport.enumerate();
315
+ device.close.mockClear();
316
+ iface.release.mockClear();
317
+ jest
318
+ .spyOn(transport as any, 'detectProtocol')
319
+ .mockRejectedValueOnce(new Error('terminal protocol probe failure'));
320
+
321
+ await expect(transport.acquire({ path })).rejects.toMatchObject({
322
+ errorCode: expect.any(Number),
323
+ });
324
+
325
+ expect(iface.release).toHaveBeenCalledTimes(1);
326
+ expect(device.close).toHaveBeenCalledTimes(1);
327
+ expect((transport as any).openDevices.has(path)).toBe(false);
328
+ });
329
+
330
+ test('stop releases an acquired USB interface even before a Protocol V2 call', async () => {
331
+ const harness = createHarness();
332
+ const { transport, path, device, iface } = harness;
333
+ await transport.enumerate();
334
+ await transport.acquire({ path, expectedProtocol: 'V2' });
335
+ device.close.mockClear();
336
+ iface.release.mockClear();
337
+
338
+ await transport.stop();
339
+
340
+ expect(iface.release).toHaveBeenCalledTimes(1);
341
+ expect(device.close).toHaveBeenCalledTimes(1);
342
+ expect(transport.getProtocolType(path)).toBeUndefined();
343
+ });
344
+
345
+ test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
193
346
  const harness = createHarness();
194
347
  const { transport, path, epOut } = harness;
195
348
 
196
349
  await transport.enumerate();
197
350
  await transport.acquire({ path, expectedProtocol: 'V2' });
198
351
 
199
- expect(epOut.transfer).not.toHaveBeenCalled();
352
+ expect(epOut.makeTransfer).toHaveBeenCalledTimes(1);
200
353
  expect(transport.getProtocolType(path)).toBe('V2');
201
354
  await transport.release(path);
202
355
  });
203
356
 
204
- test('keeps seq across calls and reacquire without probing explicit V2', async () => {
357
+ test('keeps seq across calls and actively probed reacquire', async () => {
205
358
  const harness = createHarness();
206
359
  const { transport, path, sentSeqs } = harness;
207
360
 
@@ -211,7 +364,7 @@ describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
211
364
  await harness.acquire();
212
365
  await transport.call(path, 'Ping', { message: 'second' });
213
366
 
214
- expect(sentSeqs).toEqual([1, 2]);
367
+ expect(sentSeqs).toEqual([1, 2, 3, 4]);
215
368
  await transport.release(path);
216
369
  });
217
370
 
@@ -219,14 +372,14 @@ describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
219
372
  const harness = createHarness();
220
373
  const { transport, path, epOut } = harness;
221
374
  await harness.acquire();
222
- epOut.transfer.mockClear();
375
+ epOut.makeTransfer.mockClear();
223
376
  harness.failNextWrite(new Error('LIBUSB_ERROR_IO'));
224
377
 
225
378
  await expect(transport.call(path, 'Ping', { message: 'write-failure' })).rejects.toThrow(
226
379
  'LIBUSB_ERROR_IO'
227
380
  );
228
381
 
229
- expect(epOut.transfer).toHaveBeenCalledTimes(1);
382
+ expect(epOut.makeTransfer).toHaveBeenCalledTimes(1);
230
383
  });
231
384
 
232
385
  test('rejects a pending read when release invalidates the link', async () => {
@@ -253,6 +406,20 @@ describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
253
406
  expect(settled).not.toBe('still pending');
254
407
  });
255
408
 
409
+ test('stop cancels an in-flight native USB transfer before releasing the interface', async () => {
410
+ const harness = createHarness();
411
+ const { transport, path } = harness;
412
+ await harness.acquire();
413
+ const pendingRead = harness.holdRead();
414
+
415
+ const call = transport.call(path, 'Ping', { message: 'pending' }, { timeoutMs: 5000 });
416
+ await pendingRead.started;
417
+ await transport.stop();
418
+
419
+ await expect(call).rejects.toThrow();
420
+ expect(harness.getCancelledTransferCount()).toBe(1);
421
+ });
422
+
256
423
  test('keeps the cursor after a response timeout rebuilds the USB connection', async () => {
257
424
  const harness = createHarness();
258
425
  const { transport, path, sentSeqs } = harness;
@@ -266,7 +433,7 @@ describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
266
433
  await harness.acquire();
267
434
  await transport.call(path, 'Ping', { message: 'after-timeout' });
268
435
 
269
- expect(sentSeqs).toEqual([1, 2]);
436
+ expect(sentSeqs).toEqual([1, 2, 3, 4]);
270
437
  await transport.release(path);
271
438
  });
272
439
  });
package/dist/index.d.ts CHANGED
@@ -2,48 +2,119 @@ import * as transport from '@onekeyfe/hd-transport';
2
2
  import transport__default, { ProtocolV2UsbTransportBase, OneKeyDeviceInfo, AcquireInput, TransportCallOptions, ProtocolV2Schemas, ProtocolV2CallContext, ProtocolType } from '@onekeyfe/hd-transport';
3
3
  import EventEmitter from 'events';
4
4
 
5
+ /**
6
+ * Node.js USB Transport — complete transport implementation using libusb.
7
+ *
8
+ * Unlike the old UsbPlugin (which was a LowlevelTransportSharedPlugin piped
9
+ * through LowlevelTransport), this class is a standalone transport that handles
10
+ * both protocol encoding/decoding and USB I/O directly.
11
+ *
12
+ * Modeled after WebUsbTransport.
13
+ */
5
14
  declare class NodeUsbTransport extends ProtocolV2UsbTransportBase<string> {
6
15
  messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
16
+ /** Protobuf schema for Protocol V2 transports. */
7
17
  messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
18
+ private protocolV2SchemaSource;
8
19
  name: string;
9
20
  version: string;
10
21
  configured: boolean;
11
22
  isOutdated: boolean;
12
23
  Log?: any;
13
24
  emitter?: EventEmitter;
25
+ /** serial → bus id, built during enumerate */
14
26
  private serialToBusId;
27
+ /** path → opened device state */
15
28
  private openDevices;
29
+ /** Per-path protocol type detected by active wire-level probe. */
16
30
  private deviceProtocol;
31
+ /** per-path reconnect lock to prevent concurrent reconnects */
17
32
  private reconnectLocks;
33
+ /**
34
+ * Retain the low-level Transfer so release()/stop() can cancel native pending reads.
35
+ * Endpoint.transfer() hides it and may keep the CLI alive after output completes.
36
+ */
37
+ private activeTransfers;
38
+ /** set to true when cancel() is called; checked by retry loops */
18
39
  private cancelled;
19
40
  constructor();
41
+ /**
42
+ * Initialize transport.
43
+ * Signature matches the Transport.init interface (logger, emitter).
44
+ */
20
45
  init(logger: any, emitter?: EventEmitter): Promise<string>;
21
46
  configure(signedData: any): Promise<void>;
22
47
  configureProtocolV2(signedData: any): void;
23
48
  listen(): void;
24
- stop(): void;
49
+ stop(): Promise<void>;
50
+ /**
51
+ * Low-level post (send only, no response). Not used by NodeUsbTransport
52
+ * since call() handles the full send+receive cycle, but required by the Transport interface.
53
+ */
25
54
  post(path: string, name: string, data: Record<string, unknown>): Promise<void>;
55
+ /**
56
+ * Low-level read (receive only). Not used by NodeUsbTransport
57
+ * since call() handles the full send+receive cycle, but required by the Transport interface.
58
+ */
26
59
  read(path: string): Promise<{
27
60
  message: {
28
61
  [key: string]: any;
29
62
  };
30
63
  type: string;
31
64
  }>;
65
+ /**
66
+ * Enumerate connected OneKey USB devices.
67
+ * Opens each device briefly to read its serial number (used as `path`),
68
+ * then closes it. acquire() re-opens from a fresh getDeviceList().
69
+ */
32
70
  enumerate(): Promise<OneKeyDeviceInfo[]>;
71
+ /**
72
+ * Acquire device — open USB device, claim interface, return path (string).
73
+ */
33
74
  acquire(input: AcquireInput): Promise<string>;
75
+ /**
76
+ * Release device — release interface and close.
77
+ */
34
78
  release(path: string, _onclose?: boolean): Promise<void>;
79
+ private cancelActiveTransfers;
80
+ private createTrackedTransfer;
81
+ private transferInOnce;
82
+ private transferOutOnce;
35
83
  private closeOpenDevice;
84
+ /**
85
+ * Call device method — encode protobuf, send packets, receive response.
86
+ * This is the core method that replaces LowlevelTransport's call + UsbPlugin's send/receive.
87
+ */
36
88
  call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
37
89
  private callProtocolV1;
38
90
  cancel(): void;
91
+ /**
92
+ * Get the current open device for a path, re-resolving from the map
93
+ * so callers always use a fresh reference after reconnect.
94
+ */
39
95
  private getOpenDevice;
40
96
  private getErrorMessage;
41
97
  private isRetryableError;
42
98
  private isUsbTransferTimeout;
43
99
  private getDeviceInterface;
100
+ /**
101
+ * Reconnect device before retrying a failed transfer (aligned with WebUsbTransport).
102
+ * Uses per-path lock to prevent concurrent reconnects to the same device.
103
+ */
44
104
  private reconnectForRetry;
105
+ /**
106
+ * Send all encoded chunks to the device with retry.
107
+ * If a chunk fails and triggers reconnect, the entire sequence restarts
108
+ * from chunk 0 because the device resets protocol state on reconnect.
109
+ */
45
110
  private sendAllChunksWithRetry;
111
+ /**
112
+ * USB IN transfer with retry and reconnect (aligned with WebUsbTransport).
113
+ */
46
114
  private transferInWithRetry;
115
+ /**
116
+ * Open a USB device by path (serial number), claim interface, cache endpoints.
117
+ */
47
118
  private openDevice;
48
119
  private drainStaleInput;
49
120
  private createProtocolMismatchError;
@@ -61,6 +132,10 @@ declare class NodeUsbTransport extends ProtocolV2UsbTransportBase<string> {
61
132
  protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
62
133
  protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
63
134
  private callProtocolV2;
135
+ /**
136
+ * Receive a complete protobuf response from the device.
137
+ * Reads 64-byte packets, strips 0x3F marker, reassembles into hex string.
138
+ */
64
139
  private receiveData;
65
140
  getProtocolType(path: string): ProtocolType | undefined;
66
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,SAAS,EAAE,EAOhB,0BAA0B,EAE3B,MAAM,wBAAwB,CAAC;AAKhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAgKhC,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC9E,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;IAEpE,IAAI,SAAsB;IAE1B,OAAO,SAAM;IAEb,UAAU,UAAS;IAEnB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAGvB,OAAO,CAAC,aAAa,CAA6B;IAGlD,OAAO,CAAC,WAAW,CAAiC;IAGpD,OAAO,CAAC,cAAc,CAAwC;IAG9D,OAAO,CAAC,cAAc,CAA0C;IAGhE,OAAO,CAAC,SAAS,CAAS;;IAc1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAMxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAOzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAOnC,MAAM;IAIN,IAAI;IAUE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9E,IAAI,CAAC,IAAI,EAAE,MAAM;;;;;;IAiBjB,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA8BxC,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAuB7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;YAMhD,eAAe;IA6BvB,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;IA0B5B,MAAM;IAUN,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,iBAAiB;YAuDX,sBAAsB;YAyCtB,mBAAmB;YA2CnB,UAAU;YA+DV,eAAe;IAiB7B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;YAwCd,yBAAyB;YAazB,uBAAuB;YA0CvB,eAAe;YAaf,eAAe;IAc7B,SAAS,CAAC,uBAAuB,IAAI,iBAAiB;IAUtD,SAAS,CAAC,sBAAsB;cAIhB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;cAOA,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC;cAqBN,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,SAAS,CAAC,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKrE,SAAS,CAAC,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK;YAInE,cAAc;YAad,WAAW;IA6CzB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -59,14 +59,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
59
59
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
60
60
  };
61
61
 
62
- const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
63
- function shouldSuppressHighVolumeCallLog(name) {
64
- return HIGH_VOLUME_CALLS.has(name);
65
- }
66
- function createTransportCallLog(name, protocol) {
67
- return { name, protocol };
68
- }
69
-
70
62
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
71
63
  const PACKET_SIZE = transport.PROTOCOL_V1_USB_PACKET_SIZE;
72
64
  const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
@@ -140,26 +132,6 @@ function readSerialNumber(dev, openDevices) {
140
132
  }
141
133
  });
142
134
  }
143
- function transferInOnce(ep, length) {
144
- return new Promise((resolve, reject) => {
145
- ep.transfer(length, (err, data) => {
146
- if (err)
147
- return reject(err);
148
- if (!data || data.length === 0)
149
- return reject(new Error('Empty USB transfer'));
150
- resolve(data);
151
- });
152
- });
153
- }
154
- function transferOutOnce(ep, data) {
155
- return new Promise((resolve, reject) => {
156
- ep.transfer(data, (err) => {
157
- if (err)
158
- return reject(err);
159
- resolve();
160
- });
161
- });
162
- }
163
135
  function skipReportByte(packet) {
164
136
  if (packet[0] === REPORT_ID) {
165
137
  return packet.subarray(1);
@@ -184,6 +156,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
184
156
  this.openDevices = new Map();
185
157
  this.deviceProtocol = new Map();
186
158
  this.reconnectLocks = new Map();
159
+ this.activeTransfers = new Map();
187
160
  this.cancelled = false;
188
161
  }
189
162
  init(logger, emitter) {
@@ -198,19 +171,49 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
198
171
  return Promise.resolve();
199
172
  }
200
173
  configureProtocolV2(signedData) {
174
+ var _a;
175
+ const schemaSource = typeof signedData === 'string'
176
+ ? signedData
177
+ : (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
178
+ if (schemaSource === this.protocolV2SchemaSource)
179
+ return;
180
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
201
181
  this.messagesV2 = parseConfigure(signedData);
182
+ this.protocolV2SchemaSource = schemaSource;
183
+ if (!hadProtocolV2Schema)
184
+ return;
202
185
  this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] schema link cleanup failed:', error); });
203
186
  }
204
187
  listen() {
205
188
  }
206
189
  stop() {
207
- this.disposeProtocolV2UsbLinks('Node USB transport stopped').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] stop link cleanup failed:', error); });
190
+ var _a;
191
+ return __awaiter(this, void 0, void 0, function* () {
192
+ this.cancelled = true;
193
+ yield this.cancelActiveTransfers();
194
+ try {
195
+ yield this.disposeProtocolV2UsbLinks('Node USB transport stopped');
196
+ }
197
+ catch (error) {
198
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] stop link cleanup failed:', error);
199
+ }
200
+ yield Promise.allSettled(this.reconnectLocks.values());
201
+ yield this.cancelActiveTransfers();
202
+ yield Promise.all(Array.from(this.openDevices.keys(), path => this.closeOpenDevice(path)));
203
+ this.reconnectLocks.clear();
204
+ this.deviceProtocol.clear();
205
+ this.serialToBusId.clear();
206
+ });
208
207
  }
209
208
  post(path, name, data) {
210
209
  return __awaiter(this, void 0, void 0, function* () {
211
210
  if (!this.messages) {
212
211
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
213
212
  }
213
+ if (this.deviceProtocol.get(path) === 'V2') {
214
+ yield this.sendProtocolV2UsbFlowControl(path, name, data);
215
+ return;
216
+ }
214
217
  const encodeBuffers = ProtocolV1.encodeMessageChunks(this.messages, name, data);
215
218
  yield this.sendAllChunksWithRetry(path, encodeBuffers);
216
219
  });
@@ -254,7 +257,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
254
257
  });
255
258
  }
256
259
  acquire(input) {
257
- var _a, _b, _c;
260
+ var _a, _b, _c, _d, _e;
258
261
  return __awaiter(this, void 0, void 0, function* () {
259
262
  this.cancelled = false;
260
263
  const path = (_a = input.path) !== null && _a !== void 0 ? _a : '';
@@ -265,22 +268,104 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
265
268
  yield this.rotateProtocolV2UsbGeneration(path, 'Node USB transport acquired');
266
269
  yield this.closeOpenDevice(path);
267
270
  yield this.openDevice(path);
268
- yield this.detectProtocol(path, input.expectedProtocol);
271
+ const descriptor = this.getOpenDevice(path).device.deviceDescriptor;
272
+ const protocolHint = input.expectedProtocol
273
+ ? undefined
274
+ : (_b = input.protocolHint) !== null && _b !== void 0 ? _b : hdShared.inferProtocolHintFromUsbId(descriptor.idVendor, descriptor.idProduct);
275
+ yield this.detectProtocol(path, input.expectedProtocol, protocolHint);
269
276
  return path;
270
277
  }
271
278
  catch (error) {
272
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('NodeUsbTransport acquire error: ', error);
273
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, (_c = error.message) !== null && _c !== void 0 ? _c : String(error));
279
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('NodeUsbTransport acquire error: ', error);
280
+ try {
281
+ yield this.release(path);
282
+ }
283
+ catch (cleanupError) {
284
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('NodeUsbTransport acquire cleanup error: ', cleanupError);
285
+ }
286
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, (_e = error.message) !== null && _e !== void 0 ? _e : String(error));
274
287
  }
275
288
  });
276
289
  }
277
290
  release(path, _onclose) {
278
291
  return __awaiter(this, void 0, void 0, function* () {
292
+ yield this.cancelActiveTransfers(path);
279
293
  yield this.invalidateProtocolV2UsbLink(path, 'Node USB transport released');
280
294
  yield this.closeOpenDevice(path);
281
295
  this.deviceProtocol.delete(path);
282
296
  });
283
297
  }
298
+ cancelActiveTransfers(path) {
299
+ return __awaiter(this, void 0, void 0, function* () {
300
+ const transfers = Array.from(this.activeTransfers.entries()).filter(([, active]) => path === undefined || active.path === path);
301
+ transfers.forEach(([transfer]) => {
302
+ try {
303
+ transfer.cancel();
304
+ }
305
+ catch (_a) {
306
+ }
307
+ });
308
+ yield Promise.allSettled(transfers.map(([, active]) => active.settled));
309
+ });
310
+ }
311
+ createTrackedTransfer(path, endpoint, callback) {
312
+ let resolveSettled = () => undefined;
313
+ const settled = new Promise(resolve => {
314
+ resolveSettled = resolve;
315
+ });
316
+ const transfer = endpoint.makeTransfer(endpoint.timeout, (error, buffer, actualLength) => {
317
+ this.activeTransfers.delete(transfer);
318
+ resolveSettled();
319
+ callback(error, buffer, actualLength);
320
+ });
321
+ this.activeTransfers.set(transfer, { path, settled, resolveSettled });
322
+ return transfer;
323
+ }
324
+ transferInOnce(path, ep, length) {
325
+ return new Promise((resolve, reject) => {
326
+ const buffer = Buffer.alloc(length);
327
+ const transfer = this.createTrackedTransfer(path, ep, (error, _submittedBuffer, actualLength) => {
328
+ if (error) {
329
+ reject(error);
330
+ return;
331
+ }
332
+ if (actualLength <= 0) {
333
+ reject(new Error('Empty USB transfer'));
334
+ return;
335
+ }
336
+ resolve(buffer.subarray(0, actualLength));
337
+ });
338
+ try {
339
+ transfer.submit(buffer);
340
+ }
341
+ catch (error) {
342
+ const active = this.activeTransfers.get(transfer);
343
+ this.activeTransfers.delete(transfer);
344
+ active === null || active === void 0 ? void 0 : active.resolveSettled();
345
+ reject(error);
346
+ }
347
+ });
348
+ }
349
+ transferOutOnce(path, ep, data) {
350
+ return new Promise((resolve, reject) => {
351
+ const transfer = this.createTrackedTransfer(path, ep, error => {
352
+ if (error) {
353
+ reject(error);
354
+ return;
355
+ }
356
+ resolve();
357
+ });
358
+ try {
359
+ transfer.submit(data);
360
+ }
361
+ catch (error) {
362
+ const active = this.activeTransfers.get(transfer);
363
+ this.activeTransfers.delete(transfer);
364
+ active === null || active === void 0 ? void 0 : active.resolveSettled();
365
+ reject(error);
366
+ }
367
+ });
368
+ }
284
369
  closeOpenDevice(path) {
285
370
  return __awaiter(this, void 0, void 0, function* () {
286
371
  const openDev = this.openDevices.get(path);
@@ -309,7 +394,6 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
309
394
  });
310
395
  }
311
396
  call(path, name, data, options) {
312
- var _a;
313
397
  return __awaiter(this, void 0, void 0, function* () {
314
398
  this.cancelled = false;
315
399
  if (!this.messages) {
@@ -322,9 +406,6 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
322
406
  if (!protocol) {
323
407
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
324
408
  }
325
- if (!shouldSuppressHighVolumeCallLog(name)) {
326
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
327
- }
328
409
  if (protocol === 'V2') {
329
410
  return this.callProtocolV2(path, name, data, options);
330
411
  }
@@ -370,6 +451,9 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
370
451
  }
371
452
  isRetryableError(error) {
372
453
  const message = this.getErrorMessage(error).toLowerCase();
454
+ if (message.includes('cancelled') || message.includes('canceled')) {
455
+ return false;
456
+ }
373
457
  return (message.includes('libusb') ||
374
458
  message.includes('transfer') ||
375
459
  message.includes('disconnected') ||
@@ -438,7 +522,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
438
522
  const packet = new Uint8Array(PACKET_SIZE);
439
523
  packet[0] = REPORT_ID;
440
524
  packet.set(new Uint8Array(buffer), 1);
441
- yield transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(packet));
525
+ yield this.transferOutOnce(path, this.getOpenDevice(path).epOut, Buffer.from(packet));
442
526
  }
443
527
  return;
444
528
  }
@@ -471,7 +555,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
471
555
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
472
556
  }
473
557
  try {
474
- return yield transferInOnce(currentDev.epIn, length);
558
+ return yield this.transferInOnce(path, currentDev.epIn, length);
475
559
  }
476
560
  catch (error) {
477
561
  lastError = error;
@@ -530,7 +614,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
530
614
  }
531
615
  epIn.timeout = TRANSFER_TIMEOUT_MS;
532
616
  epOut.timeout = TRANSFER_TIMEOUT_MS;
533
- yield this.drainStaleInput(epIn);
617
+ yield this.drainStaleInput(path, epIn);
534
618
  this.openDevices.set(path, { device: dev, iface, epIn, epOut });
535
619
  }
536
620
  catch (err) {
@@ -543,14 +627,14 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
543
627
  }
544
628
  });
545
629
  }
546
- drainStaleInput(epIn) {
630
+ drainStaleInput(path, epIn) {
547
631
  return __awaiter(this, void 0, void 0, function* () {
548
632
  const originalTimeout = epIn.timeout;
549
633
  epIn.timeout = 50;
550
634
  try {
551
635
  for (let index = 0; index < 16; index += 1) {
552
636
  try {
553
- yield transferInOnce(epIn, PACKET_SIZE);
637
+ yield this.transferInOnce(path, epIn, PACKET_SIZE);
554
638
  }
555
639
  catch (_a) {
556
640
  break;
@@ -568,13 +652,16 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
568
652
  createProtocolDetectionError() {
569
653
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
570
654
  }
571
- detectProtocol(path, expectedProtocol) {
655
+ detectProtocol(path, expectedProtocol, protocolHint) {
572
656
  var _a;
573
657
  return __awaiter(this, void 0, void 0, function* () {
574
658
  if (expectedProtocol === 'V2') {
575
- this.deviceProtocol.set(path, 'V2');
576
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
577
- return 'V2';
659
+ if (yield this.probeProtocolV2(path)) {
660
+ this.deviceProtocol.set(path, 'V2');
661
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
662
+ return 'V2';
663
+ }
664
+ throw this.createProtocolMismatchError(expectedProtocol);
578
665
  }
579
666
  if (expectedProtocol === 'V1') {
580
667
  if (yield this.probeProtocolV1(path)) {
@@ -583,17 +670,16 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
583
670
  }
584
671
  throw this.createProtocolMismatchError(expectedProtocol);
585
672
  }
586
- if (this.deviceProtocol.get(path) === 'V2' && (yield this.probeProtocolV2(path))) {
587
- this.deviceProtocol.set(path, 'V2');
588
- return 'V2';
589
- }
590
- if (yield this.probeProtocolV1(path)) {
591
- this.deviceProtocol.set(path, 'V1');
592
- return 'V1';
593
- }
594
- if (yield this.probeProtocolV2(path)) {
595
- this.deviceProtocol.set(path, 'V2');
596
- return 'V2';
673
+ const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
674
+ for (const [index, protocol] of probeOrder.entries()) {
675
+ if (index > 0) {
676
+ yield this.resetConnectionAfterProbe(path);
677
+ }
678
+ const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
679
+ if (detected) {
680
+ this.deviceProtocol.set(path, protocol);
681
+ return protocol;
682
+ }
597
683
  }
598
684
  this.deviceProtocol.delete(path);
599
685
  throw this.createProtocolDetectionError();
@@ -604,6 +690,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
604
690
  return __awaiter(this, void 0, void 0, function* () {
605
691
  yield this.rotateProtocolV2UsbGeneration(path, 'Node USB protocol probe reset');
606
692
  try {
693
+ yield this.cancelActiveTransfers(path);
607
694
  yield this.closeOpenDevice(path);
608
695
  }
609
696
  catch (error) {
@@ -695,7 +782,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
695
782
  if (this.cancelled) {
696
783
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
697
784
  }
698
- yield transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
785
+ yield this.transferOutOnce(path, this.getOpenDevice(path).epOut, Buffer.from(frame));
699
786
  });
700
787
  }
701
788
  readProtocolV2UsbPacket(path, _context) {
@@ -705,7 +792,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
705
792
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
706
793
  }
707
794
  try {
708
- const packet = yield transferInOnce(this.getOpenDevice(path).epIn, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
795
+ const packet = yield this.transferInOnce(path, this.getOpenDevice(path).epIn, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
709
796
  return new Uint8Array(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
710
797
  }
711
798
  catch (error) {
@@ -718,6 +805,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
718
805
  }
719
806
  resetProtocolV2UsbNativeLink(path, _reason) {
720
807
  return __awaiter(this, void 0, void 0, function* () {
808
+ yield this.cancelActiveTransfers(path);
721
809
  yield this.closeOpenDevice(path);
722
810
  });
723
811
  }
@@ -727,7 +815,7 @@ class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
727
815
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
728
816
  }
729
817
  createProtocolV2UsbTimeoutError(name, timeoutMs) {
730
- return new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
818
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
731
819
  }
732
820
  callProtocolV2(path, name, data, options) {
733
821
  return __awaiter(this, void 0, void 0, function* () {
@@ -1,7 +1,2 @@
1
- import type { ProtocolType } from '@onekeyfe/hd-transport';
2
- export declare function shouldSuppressHighVolumeCallLog(name: string): boolean;
3
- export declare function createTransportCallLog(name: string, protocol: ProtocolType): {
4
- name: string;
5
- protocol: ProtocolType;
6
- };
1
+ export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
7
2
  //# sourceMappingURL=transportLog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,WAE3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY;;;EAE1E"}
1
+ {"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: '../../jest.config.js',
3
+ testEnvironment: 'node',
4
+ modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-usb",
3
- "version": "1.2.0-alpha.18",
3
+ "version": "1.2.0-alpha.181",
4
4
  "description": "OneKey hardware wallet direct USB transport plugin (libusb)",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -16,14 +16,15 @@
16
16
  "scripts": {
17
17
  "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
18
18
  "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
19
+ "test": "jest",
19
20
  "lint": "eslint .",
20
21
  "lint:fix": "eslint . --fix"
21
22
  },
22
23
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.18",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.18",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.181",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.181",
25
26
  "bytebuffer": "^5.0.1",
26
27
  "usb": "^2.14.0"
27
28
  },
28
- "gitHead": "bdbc5bf74e48ca90f5c56f3079b2b974e61f7aa4"
29
+ "gitHead": "cb79652ebe130ceb01490195aa8903715f267e36"
29
30
  }
package/src/index.ts CHANGED
@@ -7,12 +7,17 @@ import transport, {
7
7
  PROTOCOL_V1_USB_PACKET_SIZE,
8
8
  PROTOCOL_V2_CHANNEL_USB,
9
9
  PROTOCOL_V2_FRAME_MAX_BYTES,
10
+ ProtocolV2LinkError,
10
11
  ProtocolV2UsbTransportBase,
11
12
  probeProtocolV2 as probeProtocolV2Helper,
12
13
  } from '@onekeyfe/hd-transport';
13
- import { ERRORS, HardwareErrorCode, ONEKEY_WEBUSB_FILTER, wait } from '@onekeyfe/hd-shared';
14
-
15
- import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
14
+ import {
15
+ ERRORS,
16
+ HardwareErrorCode,
17
+ ONEKEY_WEBUSB_FILTER,
18
+ inferProtocolHintFromUsbId,
19
+ wait,
20
+ } from '@onekeyfe/hd-shared';
16
21
 
17
22
  import type EventEmitter from 'events';
18
23
  import type {
@@ -131,31 +136,6 @@ function readSerialNumber(dev: usb.Device, openDevices?: Map<string, OpenDevice>
131
136
  });
132
137
  }
133
138
 
134
- /**
135
- * Promisified USB IN transfer (single attempt).
136
- */
137
- function transferInOnce(ep: usb.InEndpoint, length: number): Promise<Buffer> {
138
- return new Promise((resolve, reject) => {
139
- ep.transfer(length, (err: Error | undefined, data: Buffer | undefined) => {
140
- if (err) return reject(err);
141
- if (!data || data.length === 0) return reject(new Error('Empty USB transfer'));
142
- resolve(data);
143
- });
144
- });
145
- }
146
-
147
- /**
148
- * Promisified USB OUT transfer (single attempt).
149
- */
150
- function transferOutOnce(ep: usb.OutEndpoint, data: Buffer): Promise<void> {
151
- return new Promise((resolve, reject) => {
152
- ep.transfer(data, (err: Error | undefined) => {
153
- if (err) return reject(err);
154
- resolve();
155
- });
156
- });
157
- }
158
-
159
139
  /**
160
140
  * Skip the 0x3F protocol marker byte from a USB packet.
161
141
  */
@@ -188,6 +168,8 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
188
168
  /** Protobuf schema for Protocol V2 transports. */
189
169
  messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
190
170
 
171
+ private protocolV2SchemaSource: string | undefined;
172
+
191
173
  name = 'NodeUsbTransport';
192
174
 
193
175
  version = '';
@@ -212,6 +194,15 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
212
194
  /** per-path reconnect lock to prevent concurrent reconnects */
213
195
  private reconnectLocks = new Map<string, Promise<OpenDevice>>();
214
196
 
197
+ /**
198
+ * Retain the low-level Transfer so release()/stop() can cancel native pending reads.
199
+ * Endpoint.transfer() hides it and may keep the CLI alive after output completes.
200
+ */
201
+ private activeTransfers = new Map<
202
+ usb.Transfer,
203
+ { path: string; settled: Promise<void>; resolveSettled: () => void }
204
+ >();
205
+
215
206
  /** set to true when cancel() is called; checked by retry loops */
216
207
  private cancelled = false;
217
208
 
@@ -241,7 +232,17 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
241
232
  }
242
233
 
243
234
  configureProtocolV2(signedData: any) {
235
+ const schemaSource =
236
+ typeof signedData === 'string'
237
+ ? signedData
238
+ : JSON.stringify(signedData) ?? String(signedData);
239
+ if (schemaSource === this.protocolV2SchemaSource) return;
240
+
241
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
244
242
  this.messagesV2 = parseConfigure(signedData);
243
+ this.protocolV2SchemaSource = schemaSource;
244
+ if (!hadProtocolV2Schema) return;
245
+
245
246
  this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
246
247
  this.Log?.debug('[NodeUsbTransport] schema link cleanup failed:', error)
247
248
  );
@@ -251,10 +252,27 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
251
252
  // empty — could add hotplug events via usb.on('attach'/'detach')
252
253
  }
253
254
 
254
- stop() {
255
- this.disposeProtocolV2UsbLinks('Node USB transport stopped').catch(error =>
256
- this.Log?.debug('[NodeUsbTransport] stop link cleanup failed:', error)
257
- );
255
+ async stop(): Promise<void> {
256
+ this.cancelled = true;
257
+
258
+ await this.cancelActiveTransfers();
259
+
260
+ try {
261
+ await this.disposeProtocolV2UsbLinks('Node USB transport stopped');
262
+ } catch (error) {
263
+ this.Log?.debug('[NodeUsbTransport] stop link cleanup failed:', error);
264
+ }
265
+
266
+ // Reconnect may already be in flight when dispose starts. Wait for it to
267
+ // settle, then close every remaining handle so it cannot reopen USB after
268
+ // the first cleanup pass.
269
+ await Promise.allSettled(this.reconnectLocks.values());
270
+ await this.cancelActiveTransfers();
271
+ await Promise.all(Array.from(this.openDevices.keys(), path => this.closeOpenDevice(path)));
272
+
273
+ this.reconnectLocks.clear();
274
+ this.deviceProtocol.clear();
275
+ this.serialToBusId.clear();
258
276
  }
259
277
 
260
278
  /**
@@ -265,6 +283,10 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
265
283
  if (!this.messages) {
266
284
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
267
285
  }
286
+ if (this.deviceProtocol.get(path) === 'V2') {
287
+ await this.sendProtocolV2UsbFlowControl(path, name, data);
288
+ return;
289
+ }
268
290
  const encodeBuffers = ProtocolV1.encodeMessageChunks(this.messages, name, data);
269
291
  await this.sendAllChunksWithRetry(path, encodeBuffers);
270
292
  }
@@ -332,10 +354,20 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
332
354
  await this.rotateProtocolV2UsbGeneration(path, 'Node USB transport acquired');
333
355
  await this.closeOpenDevice(path);
334
356
  await this.openDevice(path);
335
- await this.detectProtocol(path, input.expectedProtocol);
357
+ const descriptor = this.getOpenDevice(path).device.deviceDescriptor;
358
+ const protocolHint = input.expectedProtocol
359
+ ? undefined
360
+ : input.protocolHint ??
361
+ inferProtocolHintFromUsbId(descriptor.idVendor, descriptor.idProduct);
362
+ await this.detectProtocol(path, input.expectedProtocol, protocolHint);
336
363
  return path;
337
364
  } catch (error: any) {
338
365
  this.Log?.debug('NodeUsbTransport acquire error: ', error);
366
+ try {
367
+ await this.release(path);
368
+ } catch (cleanupError) {
369
+ this.Log?.debug('NodeUsbTransport acquire cleanup error: ', cleanupError);
370
+ }
339
371
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, error.message ?? String(error));
340
372
  }
341
373
  }
@@ -344,11 +376,93 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
344
376
  * Release device — release interface and close.
345
377
  */
346
378
  async release(path: string, _onclose?: boolean): Promise<void> {
379
+ await this.cancelActiveTransfers(path);
347
380
  await this.invalidateProtocolV2UsbLink(path, 'Node USB transport released');
348
381
  await this.closeOpenDevice(path);
349
382
  this.deviceProtocol.delete(path);
350
383
  }
351
384
 
385
+ private async cancelActiveTransfers(path?: string): Promise<void> {
386
+ const transfers = Array.from(this.activeTransfers.entries()).filter(
387
+ ([, active]) => path === undefined || active.path === path
388
+ );
389
+ transfers.forEach(([transfer]) => {
390
+ try {
391
+ transfer.cancel();
392
+ } catch {
393
+ // A transfer may finish between the snapshot and cancel().
394
+ }
395
+ });
396
+ await Promise.allSettled(transfers.map(([, active]) => active.settled));
397
+ }
398
+
399
+ private createTrackedTransfer(
400
+ path: string,
401
+ endpoint: usb.InEndpoint | usb.OutEndpoint,
402
+ callback: (error: Error | undefined, buffer: Buffer, actualLength: number) => void
403
+ ): usb.Transfer {
404
+ let resolveSettled: () => void = () => undefined;
405
+ const settled = new Promise<void>(resolve => {
406
+ resolveSettled = resolve;
407
+ });
408
+ const transfer = endpoint.makeTransfer(endpoint.timeout, (error, buffer, actualLength) => {
409
+ this.activeTransfers.delete(transfer);
410
+ resolveSettled();
411
+ callback(error, buffer, actualLength);
412
+ });
413
+ this.activeTransfers.set(transfer, { path, settled, resolveSettled });
414
+ return transfer;
415
+ }
416
+
417
+ private transferInOnce(path: string, ep: usb.InEndpoint, length: number): Promise<Buffer> {
418
+ return new Promise((resolve, reject) => {
419
+ const buffer = Buffer.alloc(length);
420
+ const transfer = this.createTrackedTransfer(
421
+ path,
422
+ ep,
423
+ (error, _submittedBuffer, actualLength) => {
424
+ if (error) {
425
+ reject(error);
426
+ return;
427
+ }
428
+ if (actualLength <= 0) {
429
+ reject(new Error('Empty USB transfer'));
430
+ return;
431
+ }
432
+ resolve(buffer.subarray(0, actualLength));
433
+ }
434
+ );
435
+ try {
436
+ transfer.submit(buffer);
437
+ } catch (error) {
438
+ const active = this.activeTransfers.get(transfer);
439
+ this.activeTransfers.delete(transfer);
440
+ active?.resolveSettled();
441
+ reject(error);
442
+ }
443
+ });
444
+ }
445
+
446
+ private transferOutOnce(path: string, ep: usb.OutEndpoint, data: Buffer): Promise<void> {
447
+ return new Promise((resolve, reject) => {
448
+ const transfer = this.createTrackedTransfer(path, ep, error => {
449
+ if (error) {
450
+ reject(error);
451
+ return;
452
+ }
453
+ resolve();
454
+ });
455
+ try {
456
+ transfer.submit(data);
457
+ } catch (error) {
458
+ const active = this.activeTransfers.get(transfer);
459
+ this.activeTransfers.delete(transfer);
460
+ active?.resolveSettled();
461
+ reject(error);
462
+ }
463
+ });
464
+ }
465
+
352
466
  private async closeOpenDevice(path: string): Promise<void> {
353
467
  const openDev = this.openDevices.get(path);
354
468
  if (!openDev) return;
@@ -401,10 +515,6 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
401
515
  `Device protocol has not been detected for ${path}`
402
516
  );
403
517
  }
404
- if (!shouldSuppressHighVolumeCallLog(name)) {
405
- this.Log?.debug('transport call', createTransportCallLog(name, protocol));
406
- }
407
-
408
518
  if (protocol === 'V2') {
409
519
  return this.callProtocolV2(path, name, data, options);
410
520
  }
@@ -468,6 +578,12 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
468
578
 
469
579
  private isRetryableError(error: unknown): boolean {
470
580
  const message = this.getErrorMessage(error).toLowerCase();
581
+ // cancelActiveTransfers() terminates timed-out or releasing native requests.
582
+ // LIBUSB_TRANSFER_CANCELLED is not transient. Retrying would start another pending
583
+ // read on the rebuilt interface and race protocol probing against release.
584
+ if (message.includes('cancelled') || message.includes('canceled')) {
585
+ return false;
586
+ }
471
587
  return (
472
588
  message.includes('libusb') ||
473
589
  message.includes('transfer') ||
@@ -572,7 +688,7 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
572
688
  const packet = new Uint8Array(PACKET_SIZE);
573
689
  packet[0] = REPORT_ID;
574
690
  packet.set(new Uint8Array(buffer), 1);
575
- await transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(packet));
691
+ await this.transferOutOnce(path, this.getOpenDevice(path).epOut, Buffer.from(packet));
576
692
  }
577
693
  return; // all chunks sent successfully
578
694
  } catch (error) {
@@ -615,7 +731,7 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
615
731
  throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
616
732
  }
617
733
  try {
618
- return await transferInOnce(currentDev.epIn, length);
734
+ return await this.transferInOnce(path, currentDev.epIn, length);
619
735
  } catch (error) {
620
736
  lastError = error;
621
737
  if (options?.waitIndefinitelyOnTimeout && this.isUsbTransferTimeout(error)) {
@@ -695,7 +811,7 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
695
811
  epIn.timeout = TRANSFER_TIMEOUT_MS;
696
812
  epOut.timeout = TRANSFER_TIMEOUT_MS;
697
813
 
698
- await this.drainStaleInput(epIn);
814
+ await this.drainStaleInput(path, epIn);
699
815
 
700
816
  this.openDevices.set(path, { device: dev, iface, epIn, epOut });
701
817
  } catch (err) {
@@ -708,14 +824,14 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
708
824
  }
709
825
  }
710
826
 
711
- private async drainStaleInput(epIn: usb.InEndpoint): Promise<void> {
827
+ private async drainStaleInput(path: string, epIn: usb.InEndpoint): Promise<void> {
712
828
  const originalTimeout = epIn.timeout;
713
829
  epIn.timeout = 50;
714
830
  try {
715
831
  // Drain a small bounded number of packets left by the previous USB session.
716
832
  for (let index = 0; index < 16; index += 1) {
717
833
  try {
718
- await transferInOnce(epIn, PACKET_SIZE);
834
+ await this.transferInOnce(path, epIn, PACKET_SIZE);
719
835
  } catch {
720
836
  break;
721
837
  }
@@ -741,15 +857,16 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
741
857
 
742
858
  private async detectProtocol(
743
859
  path: string,
744
- expectedProtocol?: ProtocolType
860
+ expectedProtocol?: ProtocolType,
861
+ protocolHint?: ProtocolType
745
862
  ): Promise<ProtocolType> {
746
863
  if (expectedProtocol === 'V2') {
747
- // 固件升级重启后的 bootloader 不保证响应 Ping。上层显式传入 V2
748
- // 表示协议已在重启前确认;直接建立 V2 链路,由首个业务命令验证
749
- // bootloader 是否已经完成 USB 初始化。
750
- this.deviceProtocol.set(path, 'V2');
751
- this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
752
- return 'V2';
864
+ if (await this.probeProtocolV2(path)) {
865
+ this.deviceProtocol.set(path, 'V2');
866
+ this.Log?.debug(`[NodeUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
867
+ return 'V2';
868
+ }
869
+ throw this.createProtocolMismatchError(expectedProtocol);
753
870
  }
754
871
 
755
872
  if (expectedProtocol === 'V1') {
@@ -760,19 +877,19 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
760
877
  throw this.createProtocolMismatchError(expectedProtocol);
761
878
  }
762
879
 
763
- if (this.deviceProtocol.get(path) === 'V2' && (await this.probeProtocolV2(path))) {
764
- this.deviceProtocol.set(path, 'V2');
765
- return 'V2';
766
- }
880
+ const probeOrder: ProtocolType[] =
881
+ protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
767
882
 
768
- if (await this.probeProtocolV1(path)) {
769
- this.deviceProtocol.set(path, 'V1');
770
- return 'V1';
771
- }
772
-
773
- if (await this.probeProtocolV2(path)) {
774
- this.deviceProtocol.set(path, 'V2');
775
- return 'V2';
883
+ for (const [index, protocol] of probeOrder.entries()) {
884
+ if (index > 0) {
885
+ await this.resetConnectionAfterProbe(path);
886
+ }
887
+ const detected =
888
+ protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
889
+ if (detected) {
890
+ this.deviceProtocol.set(path, protocol);
891
+ return protocol;
892
+ }
776
893
  }
777
894
 
778
895
  this.deviceProtocol.delete(path);
@@ -783,6 +900,10 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
783
900
  await this.rotateProtocolV2UsbGeneration(path, 'Node USB protocol probe reset');
784
901
 
785
902
  try {
903
+ // A timed-out probe may leave an IN transfer pending. Cancel and await it before
904
+ // closing the interface, or libusb may never callback and release()/stop() will
905
+ // wait forever, surfacing as Polling timeout (809).
906
+ await this.cancelActiveTransfers(path);
786
907
  await this.closeOpenDevice(path);
787
908
  } catch (error) {
788
909
  this.Log?.debug('[NodeUsbTransport] close after protocol probe error:', error);
@@ -883,7 +1004,7 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
883
1004
  if (this.cancelled) {
884
1005
  throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
885
1006
  }
886
- await transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
1007
+ await this.transferOutOnce(path, this.getOpenDevice(path).epOut, Buffer.from(frame));
887
1008
  }
888
1009
 
889
1010
  protected async readProtocolV2UsbPacket(
@@ -895,7 +1016,8 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
895
1016
  throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
896
1017
  }
897
1018
  try {
898
- const packet = await transferInOnce(
1019
+ const packet = await this.transferInOnce(
1020
+ path,
899
1021
  this.getOpenDevice(path).epIn,
900
1022
  PROTOCOL_V2_FRAME_MAX_BYTES
901
1023
  );
@@ -911,6 +1033,7 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
911
1033
  }
912
1034
 
913
1035
  protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
1036
+ await this.cancelActiveTransfers(path);
914
1037
  await this.closeOpenDevice(path);
915
1038
  }
916
1039
 
@@ -920,7 +1043,10 @@ export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string>
920
1043
  }
921
1044
 
922
1045
  protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
923
- return new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
1046
+ return new ProtocolV2LinkError(
1047
+ 'response-timeout',
1048
+ `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
1049
+ );
924
1050
  }
925
1051
 
926
1052
  private async callProtocolV2(
@@ -1,11 +1 @@
1
- import type { ProtocolType } from '@onekeyfe/hd-transport';
2
-
3
- const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
4
-
5
- export function shouldSuppressHighVolumeCallLog(name: string) {
6
- return HIGH_VOLUME_CALLS.has(name);
7
- }
8
-
9
- export function createTransportCallLog(name: string, protocol: ProtocolType) {
10
- return { name, protocol };
11
- }
1
+ export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';