@onekeyfe/hd-transport-web-device 1.2.7 → 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.
- package/__tests__/electron-ble-transport.test.ts +20 -23
- package/__tests__/webusb-protocol-cache.test.ts +121 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +9 -3
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +111 -13
- package/dist/webusb.d.ts +7 -2
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +1 -5
- package/src/webusb.ts +117 -10
|
@@ -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: '
|
|
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
|
|
194
|
-
errorCode: HardwareErrorCode.
|
|
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.
|
|
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
|
|
646
|
-
const device = { id: '
|
|
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
|
|
651
|
-
errorCode: HardwareErrorCode.
|
|
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.
|
|
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
|
|
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
|
|
674
|
-
errorCode: HardwareErrorCode.
|
|
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.
|
|
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('
|
|
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('
|
|
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
|
|
784
|
-
const device = { id: '
|
|
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
|
|
789
|
-
errorCode: HardwareErrorCode.
|
|
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.
|
|
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(
|
|
242
|
-
'
|
|
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":";
|
|
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<
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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<
|
|
47
|
+
connect(path: string, first: boolean): Promise<undefined>;
|
|
48
48
|
private discoverEndpoints;
|
|
49
|
-
connectToDevice(path: string, first: boolean): Promise<
|
|
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;
|
package/dist/webusb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;
|
|
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.
|
|
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.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
/**
|