@onekeyfe/hd-transport-web-device 1.2.8-alpha.10 → 1.2.8-alpha.101
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 +45 -30
- package/__tests__/webusb-identity-probe.test.ts +887 -0
- package/__tests__/webusb-protocol-cache.test.ts +121 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +27 -8
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +24 -3
- package/dist/index.js +405 -35
- package/dist/webusb.d.ts +24 -3
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +10 -15
- package/src/webusb.ts +400 -35
|
@@ -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';
|
|
@@ -102,7 +102,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
102
102
|
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
test('reports
|
|
105
|
+
test('reports a typed probe failure when automatic detection exhausts both protocols', async () => {
|
|
106
106
|
const webusb = new WebUsbTransport() as any;
|
|
107
107
|
const path = 'unresponsive-webusb';
|
|
108
108
|
webusb.probeProtocolV1 = jest.fn().mockResolvedValue(false);
|
|
@@ -111,7 +111,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
111
111
|
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
112
112
|
|
|
113
113
|
await expect(webusb.detectProtocol(path)).rejects.toMatchObject({
|
|
114
|
-
errorCode: HardwareErrorCode.
|
|
114
|
+
errorCode: HardwareErrorCode.TransportProtocolProbeFailed,
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
expect(webusb.probeProtocolV1).toHaveBeenCalledTimes(1);
|
|
@@ -154,7 +154,20 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
154
154
|
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
test('reports a
|
|
157
|
+
test('reports a strict V1 probe miss without falling back to another protocol', async () => {
|
|
158
|
+
const webusb = new WebUsbTransport() as any;
|
|
159
|
+
webusb.probeProtocolV1 = jest.fn().mockResolvedValue(false);
|
|
160
|
+
webusb.probeProtocolV2 = jest.fn();
|
|
161
|
+
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
162
|
+
await expect(webusb.detectProtocol('classic-webusb', 'V1')).rejects.toMatchObject({
|
|
163
|
+
errorCode: HardwareErrorCode.TransportProtocolProbeFailed,
|
|
164
|
+
params: { expectedProtocol: 'V1' },
|
|
165
|
+
});
|
|
166
|
+
expect(webusb.probeProtocolV2).not.toHaveBeenCalled();
|
|
167
|
+
expect(webusb.closeConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('reports a typed protocol probe failure only after the bounded retry is exhausted', async () => {
|
|
158
171
|
const webusb = new WebUsbTransport() as any;
|
|
159
172
|
const path = 'pro2-webusb';
|
|
160
173
|
webusb.probeProtocolV1 = jest.fn();
|
|
@@ -163,8 +176,8 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
163
176
|
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
164
177
|
|
|
165
178
|
await expect(webusb.detectProtocol(path, 'V2')).rejects.toMatchObject({
|
|
166
|
-
errorCode: HardwareErrorCode.
|
|
167
|
-
|
|
179
|
+
errorCode: HardwareErrorCode.TransportProtocolProbeFailed,
|
|
180
|
+
params: { expectedProtocol: 'V2', attempts: 2 },
|
|
168
181
|
});
|
|
169
182
|
|
|
170
183
|
expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
|
|
@@ -238,9 +251,15 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
238
251
|
webusb.resetConnectionAfterProbe = jest.fn();
|
|
239
252
|
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
240
253
|
|
|
241
|
-
await expect(
|
|
242
|
-
'
|
|
243
|
-
)
|
|
254
|
+
await expect(
|
|
255
|
+
webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })
|
|
256
|
+
).rejects.toMatchObject({
|
|
257
|
+
errorCode: HardwareErrorCode.BridgeDeviceDisconnected,
|
|
258
|
+
params: {
|
|
259
|
+
operation: 'transferIn',
|
|
260
|
+
nativeErrorMessage: 'NetworkError: transferIn device disconnected',
|
|
261
|
+
},
|
|
262
|
+
});
|
|
244
263
|
|
|
245
264
|
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
246
265
|
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
|
@@ -13,6 +13,7 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
|
|
|
13
13
|
protocolType?: ProtocolType;
|
|
14
14
|
}
|
|
15
15
|
declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
16
|
+
private readonly identityProbe;
|
|
16
17
|
messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
|
|
17
18
|
/** Protobuf schema for Protocol V2 transports. */
|
|
18
19
|
messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
|
|
@@ -29,6 +30,11 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
29
30
|
private staleProtocolPaths;
|
|
30
31
|
/** Paths currently acquired (between a successful acquire() and its release()). */
|
|
31
32
|
private acquiredPaths;
|
|
33
|
+
private identityProbeDevices;
|
|
34
|
+
private normalDevices;
|
|
35
|
+
private pendingHandleOperations;
|
|
36
|
+
private stopPromise?;
|
|
37
|
+
private identityProbeCleanupPaths;
|
|
32
38
|
/**
|
|
33
39
|
* The exact USBDevice object each cached protocol was probed against. The
|
|
34
40
|
* browser returns the same object identity for a device as long as it stays
|
|
@@ -53,11 +59,15 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
53
59
|
configurationId: number;
|
|
54
60
|
endpointId: number;
|
|
55
61
|
interfaceId: number;
|
|
56
|
-
constructor();
|
|
62
|
+
constructor(identityProbe?: boolean);
|
|
57
63
|
/**
|
|
58
64
|
* Initialize WebUSB transport
|
|
59
65
|
*/
|
|
60
66
|
init(logger: any, emitter?: EventEmitter): void;
|
|
67
|
+
stop(): Promise<void>;
|
|
68
|
+
private stopResources;
|
|
69
|
+
private assertRunning;
|
|
70
|
+
private runHandleOperation;
|
|
61
71
|
/**
|
|
62
72
|
* Announce that a USB device left. Called from the module-scoped disconnect
|
|
63
73
|
* listener, which is why it is public rather than inlined.
|
|
@@ -118,7 +128,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
118
128
|
/**
|
|
119
129
|
* Connect to device with retry mechanism
|
|
120
130
|
*/
|
|
121
|
-
connect(path: string, first: boolean): Promise<
|
|
131
|
+
connect(path: string, first: boolean): Promise<undefined>;
|
|
122
132
|
/**
|
|
123
133
|
* Discover vendor-class (0xFF) interface and its IN/OUT endpoint numbers from USB descriptors.
|
|
124
134
|
* Falls back to legacy hardcoded values if no vendor interface is found.
|
|
@@ -128,7 +138,13 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
128
138
|
* Connect to specific device.
|
|
129
139
|
* Discovers interface/endpoint numbers from USB descriptors on first connection.
|
|
130
140
|
*/
|
|
131
|
-
connectToDevice(path: string, first: boolean): Promise<
|
|
141
|
+
connectToDevice(path: string, first: boolean): Promise<undefined>;
|
|
142
|
+
private assertHandleAvailable;
|
|
143
|
+
private claimNormalHandle;
|
|
144
|
+
private releaseNormalHandles;
|
|
145
|
+
private isHandleOwnershipError;
|
|
146
|
+
private claimIdentityProbeHandle;
|
|
147
|
+
private releaseIdentityProbeHandle;
|
|
132
148
|
private closeOpenDevice;
|
|
133
149
|
private clearEndpointHalt;
|
|
134
150
|
/**
|
|
@@ -141,6 +157,11 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
141
157
|
private assertAcquired;
|
|
142
158
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
143
159
|
private getErrorMessage;
|
|
160
|
+
private getErrorName;
|
|
161
|
+
private isWebUsbIoHardwareError;
|
|
162
|
+
private isDeviceStillEnumerated;
|
|
163
|
+
private throwWebUsbIoError;
|
|
164
|
+
private isWebUsbPermissionDeniedMessage;
|
|
144
165
|
private isRetryablePacketIoError;
|
|
145
166
|
private reconnectForPacketIoRetry;
|
|
146
167
|
private getTransferInData;
|