@onekeyfe/hd-transport-web-device 1.2.8-alpha.1 → 1.2.8-alpha.2

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.
@@ -183,15 +183,15 @@ describe('ElectronBleTransport protocol detection', () => {
183
183
  });
184
184
 
185
185
  test('does not treat an error envelope from a legacy preload as a successful connect', async () => {
186
- const device = { id: 'stale-bond-id', name: 'OneKey Pro 2' };
186
+ const device = { id: 'legacy-error-id', name: 'OneKey Pro 2' };
187
187
  const nobleBle = createNobleBle(device);
188
188
  nobleBle.connect.mockResolvedValue({
189
189
  type: 'NobleBleIpcError',
190
190
  success: false,
191
191
  error: {
192
192
  name: 'HardwareError',
193
- message: 'Bluetooth pairing information is no longer valid',
194
- errorCode: HardwareErrorCode.BleBondInvalid,
193
+ message: 'Bluetooth connection failed',
194
+ errorCode: HardwareErrorCode.BleConnectedError,
195
195
  },
196
196
  } as never);
197
197
  const bleTransport = configureTransport(nobleBle);
@@ -199,7 +199,7 @@ describe('ElectronBleTransport protocol detection', () => {
199
199
  await expect(
200
200
  bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
201
201
  ).rejects.toMatchObject({
202
- errorCode: HardwareErrorCode.BleBondInvalid,
202
+ errorCode: HardwareErrorCode.BleConnectedError,
203
203
  });
204
204
  expect(nobleBle.subscribe).not.toHaveBeenCalled();
205
205
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
@@ -642,14 +642,13 @@ describe('ElectronBleTransport protocol detection', () => {
642
642
  expect(transport.getProtocolType(device.id)).toBeUndefined();
643
643
  });
644
644
 
645
- test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
646
- const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
645
+ test('fails Protocol V2 acquire immediately when subscribe reports a connection error', async () => {
646
+ const device = { id: 'subscribe-error-pro2-id', name: 'OneKey Pro 2' };
647
647
  const nobleBle = createNobleBle(device);
648
648
  nobleBle.subscribe.mockRejectedValue({
649
649
  name: 'HardwareError',
650
- message: 'Bluetooth pairing information is no longer valid',
651
- errorCode: HardwareErrorCode.BleBondInvalid,
652
- params: { nativeErrorMessage: 'Encryption is insufficient' },
650
+ message: 'Bluetooth connection failed',
651
+ errorCode: HardwareErrorCode.BleConnectedError,
653
652
  });
654
653
  const transport = configureTransport(nobleBle);
655
654
  const probe = jest.spyOn(transport as any, 'probeProtocolV2');
@@ -657,7 +656,7 @@ describe('ElectronBleTransport protocol detection', () => {
657
656
  await expect(
658
657
  transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
659
658
  ).rejects.toMatchObject({
660
- errorCode: HardwareErrorCode.BleBondInvalid,
659
+ errorCode: HardwareErrorCode.BleConnectedError,
661
660
  });
662
661
 
663
662
  expect(probe).not.toHaveBeenCalled();
@@ -665,21 +664,19 @@ describe('ElectronBleTransport protocol detection', () => {
665
664
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
666
665
  });
667
666
 
668
- test('rehydrates a structured stale-bond error before the protocol is known', async () => {
667
+ test('rehydrates a structured connection error before the protocol is known', async () => {
669
668
  const device = { id: 'reset-unknown-protocol-id', name: 'OneKey Pro 2' };
670
669
  const nobleBle = createNobleBle(device);
671
670
  nobleBle.connect.mockRejectedValue({
672
671
  name: 'HardwareError',
673
- message: 'Bluetooth pairing information is no longer valid',
674
- errorCode: HardwareErrorCode.BleBondInvalid,
675
- params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
672
+ message: 'Bluetooth connection failed',
673
+ errorCode: HardwareErrorCode.BleConnectedError,
676
674
  });
677
675
  const transport = configureTransport(nobleBle);
678
676
 
679
677
  await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
680
678
  name: 'HardwareError',
681
- errorCode: HardwareErrorCode.BleBondInvalid,
682
- params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
679
+ errorCode: HardwareErrorCode.BleConnectedError,
683
680
  });
684
681
  });
685
682
 
@@ -699,7 +696,7 @@ describe('ElectronBleTransport protocol detection', () => {
699
696
  }
700
697
  });
701
698
 
702
- test('does not classify a generic macOS connection failure as a stale bond', async () => {
699
+ test('preserves a generic macOS connection failure', async () => {
703
700
  const device = { id: 'offline-pro2-macos-id', name: 'OneKey Pro 2' };
704
701
  const nobleBle = createNobleBle(device);
705
702
  nobleBle.connect.mockRejectedValue(new Error('connection failed'));
@@ -715,7 +712,7 @@ describe('ElectronBleTransport protocol detection', () => {
715
712
  }
716
713
  });
717
714
 
718
- test('keeps stale-bond subscribe mapping out of Protocol V1 acquire', async () => {
715
+ test('preserves a native subscribe failure during Protocol V1 acquire', async () => {
719
716
  const device = { id: 'classic-v1-id', name: 'OneKey Classic' };
720
717
  const nobleBle = createNobleBle(device);
721
718
  nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
@@ -780,19 +777,19 @@ describe('ElectronBleTransport protocol detection', () => {
780
777
  }
781
778
  );
782
779
 
783
- test('preserves a native stale-bond error during an expected Protocol V2 probe', async () => {
784
- const device = { id: 'stale-bond-probe-id', name: 'OneKey Pro 2' };
780
+ test('preserves a native connection error during an expected Protocol V2 probe', async () => {
781
+ const device = { id: 'connection-error-probe-id', name: 'OneKey Pro 2' };
785
782
  const nobleBle = createNobleBle(device);
786
783
  nobleBle.write.mockRejectedValue({
787
784
  name: 'HardwareError',
788
- message: 'Bluetooth pairing information is no longer valid',
789
- errorCode: HardwareErrorCode.BleBondInvalid,
785
+ message: 'Bluetooth connection failed',
786
+ errorCode: HardwareErrorCode.BleConnectedError,
790
787
  });
791
788
  const transport = configureTransport(nobleBle);
792
789
 
793
790
  await expect(
794
791
  transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
795
- ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
792
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleConnectedError });
796
793
 
797
794
  expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
798
795
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
@@ -64,6 +64,127 @@ describe('WebUsbTransport protocol probe cache', () => {
64
64
  }
65
65
  );
66
66
 
67
+ test('reports claimInterface failures as WebUSB device access errors', async () => {
68
+ const webusb = new WebUsbTransport();
69
+ const path = 'connected-usb-device';
70
+ const claimError = Object.assign(new Error('Unable to claim interface'), {
71
+ name: 'NetworkError',
72
+ });
73
+ const device = {
74
+ opened: true,
75
+ configuration: { configurationValue: 1 },
76
+ configurations: [],
77
+ claimInterface: jest.fn().mockRejectedValue(claimError),
78
+ };
79
+ jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
80
+ jest.spyOn(webusb, 'getConnectedDevices').mockResolvedValue([]);
81
+ const state = webusb as unknown as { deviceProtocol: Map<string, 'V1' | 'V2'> };
82
+ state.deviceProtocol.set(path, 'V1');
83
+
84
+ await expect(webusb.connectToDevice(path, false)).rejects.toMatchObject({
85
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
86
+ params: {
87
+ operation: 'claimInterface',
88
+ nativeErrorName: 'NetworkError',
89
+ nativeErrorMessage: 'Unable to claim interface',
90
+ },
91
+ });
92
+ });
93
+
94
+ test('reports transferIn failures as WebUSB device access errors', async () => {
95
+ const webusb = new WebUsbTransport() as any;
96
+ const path = 'connected-usb-device';
97
+ const transferError = Object.assign(new Error('Transfer endpoint is unavailable'), {
98
+ name: 'NetworkError',
99
+ });
100
+ webusb.findDevice = jest.fn().mockResolvedValue({
101
+ opened: true,
102
+ transferIn: jest.fn().mockRejectedValue(transferError),
103
+ });
104
+
105
+ await expect(webusb.transferInWithRetry(path, 64)).rejects.toMatchObject({
106
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
107
+ params: {
108
+ operation: 'transferIn',
109
+ nativeErrorName: 'NetworkError',
110
+ nativeErrorMessage: 'Transfer endpoint is unavailable',
111
+ },
112
+ });
113
+ });
114
+
115
+ test.each(['LIBUSB_ERROR_ACCESS', "Failed to execute 'open' on 'USBDevice': Access denied."])(
116
+ 'reports USB open permission failure: %s',
117
+ async message => {
118
+ const webusb = new WebUsbTransport();
119
+ const device = {
120
+ opened: false,
121
+ open: jest
122
+ .fn()
123
+ .mockRejectedValue(Object.assign(new Error(message), { name: 'SecurityError' })),
124
+ };
125
+ jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
126
+ await expect(webusb.connect('mock-usb', true)).rejects.toMatchObject({
127
+ errorCode: HardwareErrorCode.BridgeNeedsPermission,
128
+ params: {
129
+ operation: 'open',
130
+ nativeErrorName: 'SecurityError',
131
+ nativeErrorMessage: message,
132
+ },
133
+ });
134
+ expect(device.open).toHaveBeenCalledTimes(1);
135
+ }
136
+ );
137
+
138
+ test('does not classify protected-interface SecurityError as missing OS permission', async () => {
139
+ const webusb = new WebUsbTransport();
140
+ const device = {
141
+ opened: false,
142
+ open: jest
143
+ .fn()
144
+ .mockRejectedValue(
145
+ Object.assign(new Error('Protected interface'), { name: 'SecurityError' })
146
+ ),
147
+ };
148
+ jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
149
+ await expect(webusb.connectToDevice('mock-usb', true)).rejects.toMatchObject({
150
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
151
+ });
152
+ });
153
+
154
+ test.each(['claimInterface', 'transferIn', 'transferOut'] as const)(
155
+ 'preserves USB permission failures during %s',
156
+ async operation => {
157
+ const webusb = new WebUsbTransport() as unknown as {
158
+ throwWebUsbIoError(path: string, operation: string, error: unknown): Promise<never>;
159
+ };
160
+ await expect(
161
+ webusb.throwWebUsbIoError('mock-usb', operation, new Error('LIBUSB_ERROR_ACCESS'))
162
+ ).rejects.toMatchObject({
163
+ errorCode: HardwareErrorCode.BridgeNeedsPermission,
164
+ params: { operation, nativeErrorMessage: 'LIBUSB_ERROR_ACCESS' },
165
+ });
166
+ }
167
+ );
168
+
169
+ test('fails a stalled USB write without replaying the packet', async () => {
170
+ const webusb = new WebUsbTransport();
171
+ const device = {
172
+ opened: true,
173
+ transferOut: jest.fn().mockResolvedValue({ status: 'stall', bytesWritten: 0 }),
174
+ };
175
+ jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
176
+ const state = webusb as unknown as {
177
+ transferOutWithRetry(path: string, packet: Uint8Array): Promise<void>;
178
+ };
179
+ await expect(state.transferOutWithRetry('mock-usb', new Uint8Array([1]))).rejects.toMatchObject(
180
+ {
181
+ errorCode: HardwareErrorCode.WebUsbDeviceAccessError,
182
+ params: { operation: 'transferOut', nativeErrorMessage: 'transferOut status: stall' },
183
+ }
184
+ );
185
+ expect(device.transferOut).toHaveBeenCalledTimes(1);
186
+ });
187
+
67
188
  test('acquire skips the wire probe when the protocol is already cached', async () => {
68
189
  const webusb = buildAcquirableTransport();
69
190
  const path = 'pro-webusb';
@@ -238,9 +238,15 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
238
238
  webusb.resetConnectionAfterProbe = jest.fn();
239
239
  await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
240
240
 
241
- await expect(webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })).rejects.toThrow(
242
- 'NetworkError'
243
- );
241
+ await expect(
242
+ webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })
243
+ ).rejects.toMatchObject({
244
+ errorCode: HardwareErrorCode.BridgeDeviceDisconnected,
245
+ params: {
246
+ operation: 'transferIn',
247
+ nativeErrorMessage: 'NetworkError: transferIn device disconnected',
248
+ },
249
+ });
244
250
 
245
251
  expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
246
252
  expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -118,7 +118,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
118
118
  /**
119
119
  * Connect to device with retry mechanism
120
120
  */
121
- connect(path: string, first: boolean): Promise<void>;
121
+ connect(path: string, first: boolean): Promise<undefined>;
122
122
  /**
123
123
  * Discover vendor-class (0xFF) interface and its IN/OUT endpoint numbers from USB descriptors.
124
124
  * Falls back to legacy hardcoded values if no vendor interface is found.
@@ -128,7 +128,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
128
128
  * Connect to specific device.
129
129
  * Discovers interface/endpoint numbers from USB descriptors on first connection.
130
130
  */
131
- connectToDevice(path: string, first: boolean): Promise<void>;
131
+ connectToDevice(path: string, first: boolean): Promise<undefined>;
132
132
  private closeOpenDevice;
133
133
  private clearEndpointHalt;
134
134
  /**
@@ -141,6 +141,11 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
141
141
  private assertAcquired;
142
142
  post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
143
143
  private getErrorMessage;
144
+ private getErrorName;
145
+ private isWebUsbIoHardwareError;
146
+ private isDeviceStillEnumerated;
147
+ private throwWebUsbIoError;
148
+ private isWebUsbPermissionDeniedMessage;
144
149
  private isRetryablePacketIoError;
145
150
  private reconnectForPacketIoRetry;
146
151
  private getTransferInData;
package/dist/index.js CHANGED
@@ -332,6 +332,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
332
332
  return yield this.connectToDevice(path, first);
333
333
  }
334
334
  catch (e) {
335
+ if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
336
+ throw e;
337
+ }
335
338
  if (i === maxRetries - 1) {
336
339
  throw e;
337
340
  }
@@ -373,7 +376,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
373
376
  return __awaiter(this, void 0, void 0, function* () {
374
377
  let device = yield this.findDevice(path);
375
378
  if (!device.opened) {
376
- yield device.open();
379
+ try {
380
+ yield device.open();
381
+ }
382
+ catch (error) {
383
+ return this.throwWebUsbIoError(path, 'open', error);
384
+ }
377
385
  }
378
386
  if (first || this.deviceProtocol.get(path) !== 'V1') {
379
387
  try {
@@ -386,7 +394,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
386
394
  yield this.getConnectedDevices();
387
395
  device = yield this.findDevice(path);
388
396
  if (!device.opened) {
389
- yield device.open();
397
+ try {
398
+ yield device.open();
399
+ }
400
+ catch (error) {
401
+ return this.throwWebUsbIoError(path, 'open', error);
402
+ }
390
403
  }
391
404
  if (first ||
392
405
  !device.configuration ||
@@ -395,7 +408,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
395
408
  }
396
409
  const endpoints = this.discoverEndpoints(device);
397
410
  this.deviceEndpoints.set(path, endpoints);
398
- yield device.claimInterface(endpoints.interfaceNumber);
411
+ try {
412
+ yield device.claimInterface(endpoints.interfaceNumber);
413
+ }
414
+ catch (error) {
415
+ return this.throwWebUsbIoError(path, 'claimInterface', error);
416
+ }
399
417
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
400
418
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
401
419
  });
@@ -459,8 +477,76 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
459
477
  }
460
478
  return String(error);
461
479
  }
480
+ getErrorName(error) {
481
+ if (typeof error === 'object' && error && 'name' in error) {
482
+ const { name } = error;
483
+ return typeof name === 'string' ? name : String(name !== null && name !== void 0 ? name : '');
484
+ }
485
+ return '';
486
+ }
487
+ isWebUsbIoHardwareError(error) {
488
+ return (error instanceof hdShared.HardwareError &&
489
+ (error.errorCode === hdShared.HardwareErrorCode.BridgeDeviceDisconnected ||
490
+ error.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission ||
491
+ error.errorCode === hdShared.HardwareErrorCode.WebUsbDeviceAccessError));
492
+ }
493
+ isDeviceStillEnumerated(path) {
494
+ var _a;
495
+ return __awaiter(this, void 0, void 0, function* () {
496
+ if (!this.usb)
497
+ return undefined;
498
+ try {
499
+ const devices = yield this.usb.getDevices();
500
+ return devices.some(device => hdShared.resolveOneKeyUsbDevicePath(device) === path);
501
+ }
502
+ catch (error) {
503
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] failed to verify WebUSB device presence:', error);
504
+ return undefined;
505
+ }
506
+ });
507
+ }
508
+ throwWebUsbIoError(path, operation, error) {
509
+ var _a;
510
+ return __awaiter(this, void 0, void 0, function* () {
511
+ if (this.isWebUsbIoHardwareError(error))
512
+ throw error;
513
+ if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
514
+ throw error;
515
+ const nativeError = error instanceof transport.ProtocolV2LinkError ? (_a = error.cause) !== null && _a !== void 0 ? _a : error : error;
516
+ if (this.isWebUsbIoHardwareError(nativeError))
517
+ throw nativeError;
518
+ const nativeErrorName = this.getErrorName(nativeError);
519
+ const nativeErrorMessage = this.getErrorMessage(nativeError);
520
+ if (this.isWebUsbPermissionDeniedMessage(nativeErrorMessage)) {
521
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BridgeNeedsPermission, nativeErrorMessage, {
522
+ operation,
523
+ nativeErrorName: nativeErrorName || undefined,
524
+ nativeErrorMessage,
525
+ });
526
+ }
527
+ const disconnectedByMessage = /(?:device )?disconnected|device not found|action was interrupted/i.test(`${nativeErrorName}: ${nativeErrorMessage}`);
528
+ const stillEnumerated = disconnectedByMessage
529
+ ? false
530
+ : yield this.isDeviceStillEnumerated(path);
531
+ const errorCode = disconnectedByMessage || stillEnumerated === false
532
+ ? hdShared.HardwareErrorCode.BridgeDeviceDisconnected
533
+ : hdShared.HardwareErrorCode.WebUsbDeviceAccessError;
534
+ throw hdShared.ERRORS.TypedError(errorCode, nativeErrorMessage || undefined, {
535
+ operation,
536
+ nativeErrorName: nativeErrorName || undefined,
537
+ nativeErrorMessage: nativeErrorMessage || undefined,
538
+ });
539
+ });
540
+ }
541
+ isWebUsbPermissionDeniedMessage(message) {
542
+ return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
543
+ }
462
544
  isRetryablePacketIoError(error) {
545
+ if (this.isWebUsbIoHardwareError(error))
546
+ return false;
463
547
  const message = this.getErrorMessage(error).toLowerCase();
548
+ if (this.isWebUsbPermissionDeniedMessage(message))
549
+ return false;
464
550
  return (message.includes('transferout') ||
465
551
  message.includes('transferin') ||
466
552
  message.includes('usbdevice') ||
@@ -525,7 +611,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
525
611
  lastError = error;
526
612
  const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
527
613
  if (!shouldRetry) {
528
- throw error;
614
+ return this.throwWebUsbIoError(path, 'transferOut', error);
529
615
  }
530
616
  try {
531
617
  yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
@@ -536,7 +622,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
536
622
  }
537
623
  }
538
624
  }
539
- throw lastError;
625
+ return this.throwWebUsbIoError(path, 'transferOut', lastError);
540
626
  });
541
627
  }
542
628
  transferOutOnce(path, packet) {
@@ -549,7 +635,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
549
635
  const endpoints = this.deviceEndpoints.get(path);
550
636
  const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
551
637
  const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
552
- yield device.transferOut(endpointOut, transferBuffer);
638
+ const result = yield device.transferOut(endpointOut, transferBuffer);
639
+ if (result.status !== 'ok') {
640
+ return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
641
+ }
553
642
  });
554
643
  }
555
644
  transferInWithRetry(path, length, cancelToken) {
@@ -577,7 +666,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
577
666
  }
578
667
  const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
579
668
  if (!shouldRetry) {
580
- throw error;
669
+ return this.throwWebUsbIoError(path, 'transferIn', error);
581
670
  }
582
671
  try {
583
672
  yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
@@ -588,7 +677,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
588
677
  }
589
678
  }
590
679
  }
591
- throw lastError;
680
+ return this.throwWebUsbIoError(path, 'transferIn', lastError);
592
681
  });
593
682
  }
594
683
  transferInOnce(path, length) {
@@ -665,7 +754,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
665
754
  });
666
755
  return true;
667
756
  }
668
- catch (_error) {
757
+ catch (error) {
758
+ if (this.isWebUsbIoHardwareError(error))
759
+ throw error;
669
760
  return false;
670
761
  }
671
762
  });
@@ -680,6 +771,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
680
771
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
681
772
  logger: this.Log,
682
773
  logPrefix: 'ProtocolV2 WebUSB',
774
+ shouldRethrow: error => this.isWebUsbIoHardwareError(error),
683
775
  });
684
776
  });
685
777
  }
@@ -726,7 +818,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
726
818
  }
727
819
  callProtocolV2(path, name, data, options) {
728
820
  return __awaiter(this, void 0, void 0, function* () {
729
- return this.callProtocolV2Usb(path, name, data, options);
821
+ try {
822
+ return yield this.callProtocolV2Usb(path, name, data, options);
823
+ }
824
+ catch (error) {
825
+ if (!(error instanceof transport.ProtocolV2LinkError) || error.code !== 'io')
826
+ throw error;
827
+ const operation = error.message.includes(' USB write failed:') ? 'transferOut' : 'transferIn';
828
+ return this.throwWebUsbIoError(path, operation, error);
829
+ }
730
830
  });
731
831
  }
732
832
  receiveData(path, timeoutMs) {
@@ -1289,9 +1389,7 @@ class ElectronBleTransport {
1289
1389
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1290
1390
  this.resetProtocolV2Frames(uuid);
1291
1391
  },
1292
- shouldRethrow: error => expectedProtocol === 'V2' ||
1293
- hdShared.isBleStaleBondHardwareError(error) ||
1294
- transport.isProtocolV2LinkDisabledError(error),
1392
+ shouldRethrow: error => expectedProtocol === 'V2' || transport.isProtocolV2LinkDisabledError(error),
1295
1393
  });
1296
1394
  if (!detected) {
1297
1395
  this.clearProbeProtocol(uuid, 'V2');
package/dist/webusb.d.ts CHANGED
@@ -44,14 +44,19 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
44
44
  private createProtocolDetectionError;
45
45
  private detectProtocol;
46
46
  findDevice(path: string): Promise<USBDevice>;
47
- connect(path: string, first: boolean): Promise<void>;
47
+ connect(path: string, first: boolean): Promise<undefined>;
48
48
  private discoverEndpoints;
49
- connectToDevice(path: string, first: boolean): Promise<void>;
49
+ connectToDevice(path: string, first: boolean): Promise<undefined>;
50
50
  private closeOpenDevice;
51
51
  private clearEndpointHalt;
52
52
  private assertAcquired;
53
53
  post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
54
54
  private getErrorMessage;
55
+ private getErrorName;
56
+ private isWebUsbIoHardwareError;
57
+ private isDeviceStillEnumerated;
58
+ private throwWebUsbIoError;
59
+ private isWebUsbPermissionDeniedMessage;
55
60
  private isRetryablePacketIoError;
56
61
  private reconnectForPacketIoRetry;
57
62
  private getTransferInData;
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.8-alpha.1",
3
+ "version": "1.2.8-alpha.2",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -21,13 +21,13 @@
21
21
  "lint:fix": "eslint . --fix"
22
22
  },
23
23
  "dependencies": {
24
- "@onekeyfe/hd-shared": "1.2.8-alpha.1",
25
- "@onekeyfe/hd-transport": "1.2.8-alpha.1"
24
+ "@onekeyfe/hd-shared": "1.2.8-alpha.2",
25
+ "@onekeyfe/hd-transport": "1.2.8-alpha.2"
26
26
  },
27
27
  "devDependencies": {
28
- "@onekeyfe/hd-transport-electron": "1.2.8-alpha.1",
28
+ "@onekeyfe/hd-transport-electron": "1.2.8-alpha.2",
29
29
  "@types/w3c-web-usb": "^1.0.6",
30
30
  "@types/web-bluetooth": "^0.0.17"
31
31
  },
32
- "gitHead": "df614dc773e3e1075e5fbcdaee62d10a7a641eed"
32
+ "gitHead": "74ac64464df3806ef44ad54d313378754c3eaed2"
33
33
  }
@@ -18,7 +18,6 @@ import {
18
18
  HardwareErrorCode,
19
19
  HardwareErrorCodeMessage,
20
20
  createDeferred,
21
- isBleStaleBondHardwareError,
22
21
  isHeaderChunk,
23
22
  } from '@onekeyfe/hd-shared';
24
23
 
@@ -676,10 +675,7 @@ export default class ElectronBleTransport {
676
675
  this.resetProtocolV2Frames(uuid);
677
676
  },
678
677
  // A declared V2 protocol needs no fallback; preserve the actual link failure.
679
- shouldRethrow: error =>
680
- expectedProtocol === 'V2' ||
681
- isBleStaleBondHardwareError(error) ||
682
- isProtocolV2LinkDisabledError(error),
678
+ shouldRethrow: error => expectedProtocol === 'V2' || isProtocolV2LinkDisabledError(error),
683
679
  });
684
680
  if (!detected) {
685
681
  this.clearProbeProtocol(uuid, 'V2');
package/src/webusb.ts CHANGED
@@ -13,6 +13,7 @@ import transport, {
13
13
  } from '@onekeyfe/hd-transport';
14
14
  import {
15
15
  ERRORS,
16
+ HardwareError,
16
17
  HardwareErrorCode,
17
18
  ONEKEY_WEBUSB_FILTER,
18
19
  inferProtocolHintFromUsbId,
@@ -70,6 +71,8 @@ interface TransferCancelToken {
70
71
  cancelled: boolean;
71
72
  }
72
73
 
74
+ type WebUsbOperation = 'open' | 'claimInterface' | 'transferIn' | 'transferOut';
75
+
73
76
  /**
74
77
  * The navigator.usb disconnect listener is module-scoped and attached at most
75
78
  * once: listeners on navigator.usb are global and never garbage collected, so a
@@ -516,6 +519,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
516
519
  try {
517
520
  return await this.connectToDevice(path, first);
518
521
  } catch (e) {
522
+ if (e instanceof HardwareError && e.errorCode === HardwareErrorCode.BridgeNeedsPermission) {
523
+ throw e;
524
+ }
519
525
  if (i === maxRetries - 1) {
520
526
  throw e;
521
527
  }
@@ -564,7 +570,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
564
570
  async connectToDevice(path: string, first: boolean) {
565
571
  let device: USBDevice = await this.findDevice(path);
566
572
  if (!device.opened) {
567
- await device.open();
573
+ try {
574
+ await device.open();
575
+ } catch (error) {
576
+ return this.throwWebUsbIoError(path, 'open', error);
577
+ }
568
578
  }
569
579
  // A V1 packet retry continues an in-flight exchange. Preserve its endpoint
570
580
  // state as in the legacy transport; probing and V2 recovery still reset.
@@ -578,7 +588,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
578
588
  await this.getConnectedDevices();
579
589
  device = await this.findDevice(path);
580
590
  if (!device.opened) {
581
- await device.open();
591
+ try {
592
+ await device.open();
593
+ } catch (error) {
594
+ return this.throwWebUsbIoError(path, 'open', error);
595
+ }
582
596
  }
583
597
 
584
598
  if (
@@ -592,7 +606,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
592
606
  // Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
593
607
  const endpoints = this.discoverEndpoints(device);
594
608
  this.deviceEndpoints.set(path, endpoints);
595
- await device.claimInterface(endpoints.interfaceNumber);
609
+ try {
610
+ await device.claimInterface(endpoints.interfaceNumber);
611
+ } catch (error) {
612
+ return this.throwWebUsbIoError(path, 'claimInterface', error);
613
+ }
596
614
  await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
597
615
  await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
598
616
  }
@@ -662,8 +680,82 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
662
680
  return String(error);
663
681
  }
664
682
 
683
+ private getErrorName(error: unknown) {
684
+ if (typeof error === 'object' && error && 'name' in error) {
685
+ const { name } = error as { name?: unknown };
686
+ return typeof name === 'string' ? name : String(name ?? '');
687
+ }
688
+ return '';
689
+ }
690
+
691
+ private isWebUsbIoHardwareError(error: unknown): error is HardwareError {
692
+ return (
693
+ error instanceof HardwareError &&
694
+ (error.errorCode === HardwareErrorCode.BridgeDeviceDisconnected ||
695
+ error.errorCode === HardwareErrorCode.BridgeNeedsPermission ||
696
+ error.errorCode === HardwareErrorCode.WebUsbDeviceAccessError)
697
+ );
698
+ }
699
+
700
+ private async isDeviceStillEnumerated(path: string): Promise<boolean | undefined> {
701
+ if (!this.usb) return undefined;
702
+ try {
703
+ const devices = await this.usb.getDevices();
704
+ return devices.some(device => resolveOneKeyUsbDevicePath(device) === path);
705
+ } catch (error) {
706
+ this.Log?.debug('[WebUsbTransport] failed to verify WebUSB device presence:', error);
707
+ return undefined;
708
+ }
709
+ }
710
+
711
+ private async throwWebUsbIoError(
712
+ path: string,
713
+ operation: WebUsbOperation,
714
+ error: unknown
715
+ ): Promise<never> {
716
+ if (this.isWebUsbIoHardwareError(error)) throw error;
717
+ if (error instanceof ProtocolV2LinkError && error.code !== 'io') throw error;
718
+
719
+ const nativeError = error instanceof ProtocolV2LinkError ? error.cause ?? error : error;
720
+ if (this.isWebUsbIoHardwareError(nativeError)) throw nativeError;
721
+
722
+ const nativeErrorName = this.getErrorName(nativeError);
723
+ const nativeErrorMessage = this.getErrorMessage(nativeError);
724
+ // SecurityError alone may indicate a protected interface, not OS permissions.
725
+ if (this.isWebUsbPermissionDeniedMessage(nativeErrorMessage)) {
726
+ throw ERRORS.TypedError(HardwareErrorCode.BridgeNeedsPermission, nativeErrorMessage, {
727
+ operation,
728
+ nativeErrorName: nativeErrorName || undefined,
729
+ nativeErrorMessage,
730
+ });
731
+ }
732
+ const disconnectedByMessage =
733
+ /(?:device )?disconnected|device not found|action was interrupted/i.test(
734
+ `${nativeErrorName}: ${nativeErrorMessage}`
735
+ );
736
+ const stillEnumerated = disconnectedByMessage
737
+ ? false
738
+ : await this.isDeviceStillEnumerated(path);
739
+ const errorCode =
740
+ disconnectedByMessage || stillEnumerated === false
741
+ ? HardwareErrorCode.BridgeDeviceDisconnected
742
+ : HardwareErrorCode.WebUsbDeviceAccessError;
743
+
744
+ throw ERRORS.TypedError(errorCode, nativeErrorMessage || undefined, {
745
+ operation,
746
+ nativeErrorName: nativeErrorName || undefined,
747
+ nativeErrorMessage: nativeErrorMessage || undefined,
748
+ });
749
+ }
750
+
751
+ private isWebUsbPermissionDeniedMessage(message: string) {
752
+ return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
753
+ }
754
+
665
755
  private isRetryablePacketIoError(error: unknown) {
756
+ if (this.isWebUsbIoHardwareError(error)) return false;
666
757
  const message = this.getErrorMessage(error).toLowerCase();
758
+ if (this.isWebUsbPermissionDeniedMessage(message)) return false;
667
759
  return (
668
760
  message.includes('transferout') ||
669
761
  message.includes('transferin') ||
@@ -742,7 +834,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
742
834
  lastError = error;
743
835
  const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
744
836
  if (!shouldRetry) {
745
- throw error;
837
+ return this.throwWebUsbIoError(path, 'transferOut', error);
746
838
  }
747
839
  try {
748
840
  await this.reconnectForPacketIoRetry(path, 'out', attempt, error);
@@ -756,7 +848,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
756
848
  }
757
849
  }
758
850
  }
759
- throw lastError;
851
+ return this.throwWebUsbIoError(path, 'transferOut', lastError);
760
852
  }
761
853
 
762
854
  private async transferOutOnce(path: string, packet: Uint8Array) {
@@ -769,7 +861,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
769
861
  const transferBuffer = this.toArrayBuffer(
770
862
  packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
771
863
  );
772
- await device.transferOut(endpointOut, transferBuffer);
864
+ const result = await device.transferOut(endpointOut, transferBuffer);
865
+ if (result.status !== 'ok') {
866
+ return this.throwWebUsbIoError(
867
+ path,
868
+ 'transferOut',
869
+ new Error(`transferOut status: ${String(result.status)}`)
870
+ );
871
+ }
773
872
  }
774
873
 
775
874
  private async transferInWithRetry(
@@ -798,7 +897,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
798
897
  }
799
898
  const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
800
899
  if (!shouldRetry) {
801
- throw error;
900
+ return this.throwWebUsbIoError(path, 'transferIn', error);
802
901
  }
803
902
  try {
804
903
  await this.reconnectForPacketIoRetry(path, 'in', attempt, error);
@@ -812,7 +911,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
812
911
  }
813
912
  }
814
913
  }
815
- throw lastError;
914
+ return this.throwWebUsbIoError(path, 'transferIn', lastError);
816
915
  }
817
916
 
818
917
  private async transferInOnce(path: string, length: number): Promise<DataView> {
@@ -894,7 +993,8 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
894
993
  }
895
994
  );
896
995
  return true;
897
- } catch (_error) {
996
+ } catch (error) {
997
+ if (this.isWebUsbIoHardwareError(error)) throw error;
898
998
  return false;
899
999
  }
900
1000
  }
@@ -909,6 +1009,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
909
1009
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
910
1010
  logger: this.Log,
911
1011
  logPrefix: 'ProtocolV2 WebUSB',
1012
+ shouldRethrow: error => this.isWebUsbIoHardwareError(error),
912
1013
  });
913
1014
  }
914
1015
 
@@ -986,7 +1087,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
986
1087
  data: Record<string, unknown>,
987
1088
  options?: TransportCallOptions
988
1089
  ) {
989
- return this.callProtocolV2Usb(path, name, data, options);
1090
+ try {
1091
+ return await this.callProtocolV2Usb(path, name, data, options);
1092
+ } catch (error) {
1093
+ if (!(error instanceof ProtocolV2LinkError) || error.code !== 'io') throw error;
1094
+ const operation = error.message.includes(' USB write failed:') ? 'transferOut' : 'transferIn';
1095
+ return this.throwWebUsbIoError(path, operation, error);
1096
+ }
990
1097
  }
991
1098
 
992
1099
  /**