@onekeyfe/hd-transport-web-device 1.2.8-alpha.16 → 1.2.8-alpha.18
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__/webusb-identity-probe.test.ts +250 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +148 -10
- package/dist/webusb.d.ts +6 -0
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/webusb.ts +139 -10
|
@@ -2,8 +2,11 @@ import { ERRORS, HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
|
2
2
|
|
|
3
3
|
import WebUsbTransport from '../src/webusb';
|
|
4
4
|
import { probeUsbDeviceIdentities } from '@onekeyfe/hd-common-connect-sdk/src';
|
|
5
|
+
import Core from '@onekeyfe/hd-core/src/core';
|
|
6
|
+
import TransportManager from '@onekeyfe/hd-core/src/data-manager/TransportManager';
|
|
7
|
+
import { Device } from '@onekeyfe/hd-core/src/device/Device';
|
|
5
8
|
|
|
6
|
-
import type { MessageFromOneKey, TransportCallOptions } from '@onekeyfe/hd-transport';
|
|
9
|
+
import type { MessageFromOneKey, Transport, TransportCallOptions } from '@onekeyfe/hd-transport';
|
|
7
10
|
|
|
8
11
|
jest.mock('@onekeyfe/hd-core', () => jest.requireActual('../../core/src'));
|
|
9
12
|
jest.mock('../../core/src/data/config', () => ({
|
|
@@ -19,7 +22,7 @@ jest.mock('@onekeyfe/hd-transport-emulator', () => ({}));
|
|
|
19
22
|
|
|
20
23
|
type PacketIoTransport = {
|
|
21
24
|
rotateProtocolV2UsbGeneration: () => Promise<void>;
|
|
22
|
-
detectProtocol: () => Promise<'V1'>;
|
|
25
|
+
detectProtocol: () => Promise<'V1' | 'V2'>;
|
|
23
26
|
transferInWithRetry: (path: string, length: number) => Promise<DataView>;
|
|
24
27
|
transferInOnce: (path: string, length: number) => Promise<DataView>;
|
|
25
28
|
transferOutWithRetry: (path: string, packet: Uint8Array) => Promise<void>;
|
|
@@ -64,6 +67,251 @@ describe('WebUSB identity probe ownership', () => {
|
|
|
64
67
|
else delete (globalThis as { navigator?: Navigator }).navigator;
|
|
65
68
|
});
|
|
66
69
|
|
|
70
|
+
function createAcquiredTransport(protocol: 'V1' | 'V2' = 'V1') {
|
|
71
|
+
const active = new WebUsbTransport();
|
|
72
|
+
active.init({ debug: jest.fn() });
|
|
73
|
+
jest
|
|
74
|
+
.spyOn(active as unknown as PacketIoTransport, 'detectProtocol')
|
|
75
|
+
.mockResolvedValue(protocol);
|
|
76
|
+
jest.spyOn(active, 'connect').mockImplementation(() => {
|
|
77
|
+
device.opened = true;
|
|
78
|
+
return Promise.resolve();
|
|
79
|
+
});
|
|
80
|
+
return active;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test.each(['V1', 'V2'] as const)(
|
|
84
|
+
'Core disposal releases a retained %s USB session for a new transport',
|
|
85
|
+
async protocol => {
|
|
86
|
+
const active = createAcquiredTransport(protocol);
|
|
87
|
+
const next = createAcquiredTransport(protocol);
|
|
88
|
+
const selected = new Device({
|
|
89
|
+
path: 'USB_PATH',
|
|
90
|
+
id: 'USB_PATH',
|
|
91
|
+
name: 'Mock device',
|
|
92
|
+
commType: 'webusb',
|
|
93
|
+
debug: false,
|
|
94
|
+
});
|
|
95
|
+
selected.keepSession = true;
|
|
96
|
+
selected.mainId = 'USB_PATH';
|
|
97
|
+
jest.spyOn(selected, 'isUsedHere').mockReturnValue(true);
|
|
98
|
+
const release = jest.spyOn(active, 'release');
|
|
99
|
+
jest.spyOn(TransportManager, 'getTransport').mockReturnValue(active as unknown as Transport);
|
|
100
|
+
const core = new Core();
|
|
101
|
+
try {
|
|
102
|
+
await active.acquire({ path: 'USB_PATH' });
|
|
103
|
+
await selected.release();
|
|
104
|
+
expect(release).not.toHaveBeenCalled();
|
|
105
|
+
expect(device.opened).toBe(true);
|
|
106
|
+
await core.dispose();
|
|
107
|
+
await core.dispose();
|
|
108
|
+
expect(device.opened).toBe(false);
|
|
109
|
+
expect(device.close).toHaveBeenCalledTimes(1);
|
|
110
|
+
await expect(next.acquire({ path: 'USB_PATH' })).resolves.toBe('USB_PATH');
|
|
111
|
+
} finally {
|
|
112
|
+
await active.release('USB_PATH');
|
|
113
|
+
await next.release('USB_PATH');
|
|
114
|
+
jest.restoreAllMocks();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
test('stop waits for a pending native open and closes its captured handle', async () => {
|
|
120
|
+
const entered = createDeferred<void>();
|
|
121
|
+
const opening = createDeferred<void>();
|
|
122
|
+
const native = Object.assign(device, {
|
|
123
|
+
open: jest.fn(async () => {
|
|
124
|
+
entered.resolve();
|
|
125
|
+
await opening.promise;
|
|
126
|
+
device.opened = true;
|
|
127
|
+
}),
|
|
128
|
+
reset: jest.fn(),
|
|
129
|
+
configurations: [],
|
|
130
|
+
configuration: { configurationValue: 1 },
|
|
131
|
+
selectConfiguration: jest.fn(),
|
|
132
|
+
claimInterface: jest.fn(),
|
|
133
|
+
clearHalt: jest.fn(),
|
|
134
|
+
});
|
|
135
|
+
const active = new WebUsbTransport();
|
|
136
|
+
active.init({ debug: jest.fn() });
|
|
137
|
+
const connecting = active.connectToDevice('USB_PATH', true);
|
|
138
|
+
const result = expect(connecting).rejects.toThrow('WebUSB transport is stopped');
|
|
139
|
+
try {
|
|
140
|
+
await entered.promise;
|
|
141
|
+
let stopped = false;
|
|
142
|
+
const stopping = active.stop().then(() => {
|
|
143
|
+
stopped = true;
|
|
144
|
+
});
|
|
145
|
+
await Promise.resolve();
|
|
146
|
+
expect(stopped).toBe(false);
|
|
147
|
+
opening.resolve();
|
|
148
|
+
await result;
|
|
149
|
+
await stopping;
|
|
150
|
+
expect(native.reset).not.toHaveBeenCalled();
|
|
151
|
+
expect(native.claimInterface).not.toHaveBeenCalled();
|
|
152
|
+
expect(device.opened).toBe(false);
|
|
153
|
+
const next = createAcquiredTransport();
|
|
154
|
+
await expect(next.acquire({ path: 'USB_PATH' })).resolves.toBe('USB_PATH');
|
|
155
|
+
await next.release('USB_PATH');
|
|
156
|
+
} finally {
|
|
157
|
+
opening.resolve();
|
|
158
|
+
await connecting.catch(() => undefined);
|
|
159
|
+
await active.release('USB_PATH');
|
|
160
|
+
jest.restoreAllMocks();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test.each(['read', 'read retry', 'write', 'write retry'])(
|
|
165
|
+
'stop rejects a late native %s result without touching the new owner',
|
|
166
|
+
async operation => {
|
|
167
|
+
const entered = createDeferred<void>();
|
|
168
|
+
const packet = createDeferred<{ status: 'ok'; data: DataView; bytesWritten: number }>();
|
|
169
|
+
Object.assign(device, {
|
|
170
|
+
transferIn: jest.fn(() => {
|
|
171
|
+
entered.resolve();
|
|
172
|
+
return packet.promise;
|
|
173
|
+
}),
|
|
174
|
+
transferOut: jest.fn(() => {
|
|
175
|
+
entered.resolve();
|
|
176
|
+
return packet.promise;
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
const active = createAcquiredTransport();
|
|
180
|
+
const next = createAcquiredTransport();
|
|
181
|
+
await active.acquire({ path: 'USB_PATH' });
|
|
182
|
+
const state = active as unknown as PacketIoTransport;
|
|
183
|
+
let call: Promise<unknown>;
|
|
184
|
+
if (operation === 'read') call = state.transferInOnce('USB_PATH', 64);
|
|
185
|
+
else if (operation === 'read retry') call = state.transferInWithRetry('USB_PATH', 64);
|
|
186
|
+
else if (operation === 'write') call = state.transferOutOnce('USB_PATH', new Uint8Array([1]));
|
|
187
|
+
else call = state.transferOutWithRetry('USB_PATH', new Uint8Array([1]));
|
|
188
|
+
const result = expect(call).rejects.toThrow('WebUSB transport is stopped');
|
|
189
|
+
try {
|
|
190
|
+
await entered.promise;
|
|
191
|
+
await active.stop();
|
|
192
|
+
await next.acquire({ path: 'USB_PATH' });
|
|
193
|
+
packet.resolve({ status: 'ok', data: new DataView(new ArrayBuffer(64)), bytesWritten: 1 });
|
|
194
|
+
await result;
|
|
195
|
+
expect(device.close).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(device.opened).toBe(true);
|
|
197
|
+
} finally {
|
|
198
|
+
packet.resolve({ status: 'ok', data: new DataView(new ArrayBuffer(64)), bytesWritten: 1 });
|
|
199
|
+
await call.catch(() => undefined);
|
|
200
|
+
await active.release('USB_PATH');
|
|
201
|
+
await next.release('USB_PATH');
|
|
202
|
+
jest.restoreAllMocks();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
test.each(['in', 'out'] as const)(
|
|
208
|
+
'stop prevents a delayed transfer%s retry from closing or reopening the new owner',
|
|
209
|
+
async direction => {
|
|
210
|
+
const entered = createDeferred<void>();
|
|
211
|
+
const native = Object.assign(device, {
|
|
212
|
+
transferIn: jest.fn().mockRejectedValue(new Error('transferIn NetworkError')),
|
|
213
|
+
transferOut: jest.fn().mockRejectedValue(new Error('transferOut NetworkError')),
|
|
214
|
+
});
|
|
215
|
+
const active = createAcquiredTransport();
|
|
216
|
+
const next = createAcquiredTransport();
|
|
217
|
+
await active.acquire({ path: 'USB_PATH' });
|
|
218
|
+
const state = active as unknown as PacketIoTransport;
|
|
219
|
+
const retry = state.reconnectForPacketIoRetry.bind(active);
|
|
220
|
+
jest.spyOn(state, 'reconnectForPacketIoRetry').mockImplementation((...args) => {
|
|
221
|
+
entered.resolve();
|
|
222
|
+
return retry(...args);
|
|
223
|
+
});
|
|
224
|
+
const call =
|
|
225
|
+
direction === 'in'
|
|
226
|
+
? state.transferInWithRetry('USB_PATH', 64)
|
|
227
|
+
: state.transferOutWithRetry('USB_PATH', new Uint8Array([1]));
|
|
228
|
+
const result = expect(call).rejects.toThrow('WebUSB transport is stopped');
|
|
229
|
+
try {
|
|
230
|
+
await entered.promise;
|
|
231
|
+
await active.stop();
|
|
232
|
+
await next.acquire({ path: 'USB_PATH' });
|
|
233
|
+
await result;
|
|
234
|
+
expect(native[direction === 'in' ? 'transferIn' : 'transferOut']).toHaveBeenCalledTimes(1);
|
|
235
|
+
expect(device.close).toHaveBeenCalledTimes(1);
|
|
236
|
+
expect(device.opened).toBe(true);
|
|
237
|
+
} finally {
|
|
238
|
+
await call.catch(() => undefined);
|
|
239
|
+
await active.release('USB_PATH');
|
|
240
|
+
await next.release('USB_PATH');
|
|
241
|
+
jest.restoreAllMocks();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
test('failed stop keeps ownership until a later discovery confirms native closure', async () => {
|
|
247
|
+
const active = createAcquiredTransport();
|
|
248
|
+
const next = createAcquiredTransport();
|
|
249
|
+
const failure = new Error('native close failed');
|
|
250
|
+
await active.acquire({ path: 'USB_PATH' });
|
|
251
|
+
device.close.mockRejectedValueOnce(failure).mockRejectedValueOnce(failure);
|
|
252
|
+
try {
|
|
253
|
+
await expect(active.stop()).rejects.toBe(failure);
|
|
254
|
+
await next.getConnectedDevices();
|
|
255
|
+
await expect(next.acquire({ path: 'USB_PATH' })).rejects.toThrow(
|
|
256
|
+
'USB device is already in use'
|
|
257
|
+
);
|
|
258
|
+
expect(device.opened).toBe(true);
|
|
259
|
+
await next.getConnectedDevices();
|
|
260
|
+
expect(device.opened).toBe(false);
|
|
261
|
+
await expect(next.acquire({ path: 'USB_PATH' })).resolves.toBe('USB_PATH');
|
|
262
|
+
expect(device.close).toHaveBeenCalledTimes(3);
|
|
263
|
+
} finally {
|
|
264
|
+
await active.release('USB_PATH');
|
|
265
|
+
await next.release('USB_PATH');
|
|
266
|
+
jest.restoreAllMocks();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('stop closes captured handles across paths without closing re-enumerated or foreign handles', async () => {
|
|
271
|
+
const other = {
|
|
272
|
+
...device,
|
|
273
|
+
serialNumber: 'OTHER_PATH',
|
|
274
|
+
close: jest.fn(() => {
|
|
275
|
+
other.opened = false;
|
|
276
|
+
return Promise.resolve();
|
|
277
|
+
}),
|
|
278
|
+
};
|
|
279
|
+
const foreign = { ...device, serialNumber: 'FOREIGN_PATH', close: jest.fn() };
|
|
280
|
+
usb.getDevices.mockResolvedValue([device, other, foreign]);
|
|
281
|
+
const active = createAcquiredTransport();
|
|
282
|
+
jest.spyOn(active, 'connect').mockImplementation(path => {
|
|
283
|
+
(path === 'USB_PATH' ? device : other).opened = true;
|
|
284
|
+
return Promise.resolve();
|
|
285
|
+
});
|
|
286
|
+
const current = createAcquiredTransport();
|
|
287
|
+
jest.spyOn(current, 'connect').mockImplementation(() => {
|
|
288
|
+
foreign.opened = true;
|
|
289
|
+
return Promise.resolve();
|
|
290
|
+
});
|
|
291
|
+
const currentDisconnect = jest.spyOn(current, 'markProtocolStale');
|
|
292
|
+
try {
|
|
293
|
+
await active.acquire({ path: 'USB_PATH' });
|
|
294
|
+
await active.acquire({ path: 'OTHER_PATH' });
|
|
295
|
+
await current.acquire({ path: 'FOREIGN_PATH' });
|
|
296
|
+
const replacement = { ...device, opened: false, close: jest.fn() };
|
|
297
|
+
usb.getDevices.mockResolvedValue([replacement, foreign]);
|
|
298
|
+
await active.getConnectedDevices();
|
|
299
|
+
await active.stop();
|
|
300
|
+
expect(device.opened).toBe(false);
|
|
301
|
+
expect(other.opened).toBe(false);
|
|
302
|
+
expect(replacement.close).not.toHaveBeenCalled();
|
|
303
|
+
expect(foreign.close).not.toHaveBeenCalled();
|
|
304
|
+
expect(foreign.opened).toBe(true);
|
|
305
|
+
disconnectListener({ device: replacement });
|
|
306
|
+
expect(currentDisconnect).toHaveBeenCalledWith('USB_PATH');
|
|
307
|
+
} finally {
|
|
308
|
+
await active.release('USB_PATH');
|
|
309
|
+
await active.release('OTHER_PATH');
|
|
310
|
+
await current.release('FOREIGN_PATH');
|
|
311
|
+
jest.restoreAllMocks();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
67
315
|
test('does not close a handle already opened by another operation', async () => {
|
|
68
316
|
const probe = new WebUsbTransport(true);
|
|
69
317
|
probe.init({ debug: jest.fn() });
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
32
32
|
private acquiredPaths;
|
|
33
33
|
private identityProbeDevices;
|
|
34
34
|
private normalDevices;
|
|
35
|
+
private pendingHandleOperations;
|
|
36
|
+
private stopPromise?;
|
|
35
37
|
private identityProbeCleanupPaths;
|
|
36
38
|
/**
|
|
37
39
|
* The exact USBDevice object each cached protocol was probed against. The
|
|
@@ -62,6 +64,10 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
62
64
|
* Initialize WebUSB transport
|
|
63
65
|
*/
|
|
64
66
|
init(logger: any, emitter?: EventEmitter): void;
|
|
67
|
+
stop(): Promise<void>;
|
|
68
|
+
private stopResources;
|
|
69
|
+
private assertRunning;
|
|
70
|
+
private runHandleOperation;
|
|
65
71
|
/**
|
|
66
72
|
* Announce that a USB device left. Called from the module-scoped disconnect
|
|
67
73
|
* listener, which is why it is public rather than inlined.
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
90
90
|
this.acquiredPaths = new Set();
|
|
91
91
|
this.identityProbeDevices = new Map();
|
|
92
92
|
this.normalDevices = new Map();
|
|
93
|
+
this.pendingHandleOperations = new Set();
|
|
93
94
|
this.identityProbeCleanupPaths = new Set();
|
|
94
95
|
this.probedDeviceObjects = new Map();
|
|
95
96
|
this.deviceEndpoints = new Map();
|
|
@@ -112,6 +113,102 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
112
113
|
if (!this.identityProbe)
|
|
113
114
|
registerActiveWebUsbTransport(this, usb);
|
|
114
115
|
}
|
|
116
|
+
stop() {
|
|
117
|
+
this.stopped = true;
|
|
118
|
+
this.acquiredPaths.clear();
|
|
119
|
+
if (activeWebUsbTransport === this)
|
|
120
|
+
activeWebUsbTransport = undefined;
|
|
121
|
+
if (!this.stopPromise) {
|
|
122
|
+
this.stopPromise = this.stopResources().catch(error => {
|
|
123
|
+
this.stopPromise = undefined;
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return this.stopPromise;
|
|
128
|
+
}
|
|
129
|
+
stopResources() {
|
|
130
|
+
var _a, _b, _c;
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
let cleanupError;
|
|
133
|
+
try {
|
|
134
|
+
yield this.disposeProtocolV2UsbLinks('WebUSB transport stopped');
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
cleanupError = error;
|
|
138
|
+
}
|
|
139
|
+
yield Promise.allSettled([...this.pendingHandleOperations]);
|
|
140
|
+
const handles = this.identityProbe
|
|
141
|
+
? [...this.identityProbeDevices].map(([path, device]) => [device, path])
|
|
142
|
+
: [...this.normalDevices];
|
|
143
|
+
for (const [device, path] of handles) {
|
|
144
|
+
if (!this.identityProbe && normalHandleOwners.get(device) !== this) {
|
|
145
|
+
this.normalDevices.delete(device);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
try {
|
|
149
|
+
if (device.opened) {
|
|
150
|
+
const iface = (_b = (_a = this.deviceEndpoints.get(path)) === null || _a === void 0 ? void 0 : _a.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
151
|
+
try {
|
|
152
|
+
yield device.releaseInterface(iface);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] stop releaseInterface failed:', error);
|
|
156
|
+
}
|
|
157
|
+
yield device.close();
|
|
158
|
+
if (device.opened)
|
|
159
|
+
throw new Error('WebUSB handle remains open after stop');
|
|
160
|
+
}
|
|
161
|
+
if (this.identityProbe) {
|
|
162
|
+
identityProbeHandles.delete(device);
|
|
163
|
+
this.identityProbeDevices.delete(path);
|
|
164
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
normalHandleOwners.delete(device);
|
|
168
|
+
this.normalDevices.delete(device);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
cleanupError !== null && cleanupError !== void 0 ? cleanupError : (cleanupError = error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.deviceList = [];
|
|
177
|
+
this.deviceProtocol.clear();
|
|
178
|
+
this.confirmedDeviceProtocols.clear();
|
|
179
|
+
this.deviceProtocolHints.clear();
|
|
180
|
+
this.staleProtocolPaths.clear();
|
|
181
|
+
this.probedDeviceObjects.clear();
|
|
182
|
+
if (!this.normalDevices.size && !this.identityProbeDevices.size) {
|
|
183
|
+
this.deviceEndpoints.clear();
|
|
184
|
+
}
|
|
185
|
+
if (cleanupError) {
|
|
186
|
+
throw cleanupError instanceof Error
|
|
187
|
+
? cleanupError
|
|
188
|
+
: new Error(this.getErrorMessage(cleanupError));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
assertRunning() {
|
|
193
|
+
if (this.stopped) {
|
|
194
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB transport is stopped');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
runHandleOperation(operation) {
|
|
198
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
199
|
+
this.assertRunning();
|
|
200
|
+
const pending = Promise.resolve(operation());
|
|
201
|
+
this.pendingHandleOperations.add(pending);
|
|
202
|
+
try {
|
|
203
|
+
const result = yield pending;
|
|
204
|
+
this.assertRunning();
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.pendingHandleOperations.delete(pending);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
115
212
|
emitDeviceDisconnect(path, device) {
|
|
116
213
|
var _a, _b;
|
|
117
214
|
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
@@ -171,10 +268,24 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
171
268
|
});
|
|
172
269
|
}
|
|
173
270
|
getConnectedDevices() {
|
|
271
|
+
var _a;
|
|
174
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
this.assertRunning();
|
|
175
274
|
if (!this.usb)
|
|
176
275
|
return [];
|
|
177
276
|
const devices = yield this.usb.getDevices();
|
|
277
|
+
this.assertRunning();
|
|
278
|
+
for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
|
|
279
|
+
if (owner === null || owner === void 0 ? void 0 : owner.stopped) {
|
|
280
|
+
try {
|
|
281
|
+
yield owner.stop();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] stopped owner cleanup failed:', error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
this.assertRunning();
|
|
178
289
|
const onekeyDevices = devices.filter(dev => {
|
|
179
290
|
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
180
291
|
return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
@@ -201,6 +312,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
201
312
|
acquire(input) {
|
|
202
313
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
203
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
this.assertRunning();
|
|
204
316
|
if (!input.path)
|
|
205
317
|
return;
|
|
206
318
|
if (this.identityProbe) {
|
|
@@ -259,12 +371,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
259
371
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
260
372
|
}
|
|
261
373
|
const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
374
|
+
this.assertRunning();
|
|
262
375
|
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
263
376
|
const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
|
|
264
377
|
if (probedDevice) {
|
|
265
378
|
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
266
379
|
}
|
|
267
380
|
}
|
|
381
|
+
this.assertRunning();
|
|
268
382
|
this.acquiredPaths.add(input.path);
|
|
269
383
|
return yield Promise.resolve(input.path);
|
|
270
384
|
}
|
|
@@ -357,10 +471,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
357
471
|
return __awaiter(this, void 0, void 0, function* () {
|
|
358
472
|
const maxRetries = 5;
|
|
359
473
|
for (let i = 0; i < maxRetries; i++) {
|
|
474
|
+
this.assertRunning();
|
|
360
475
|
try {
|
|
361
476
|
return yield this.connectToDevice(path, first);
|
|
362
477
|
}
|
|
363
478
|
catch (e) {
|
|
479
|
+
this.assertRunning();
|
|
364
480
|
if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
|
|
365
481
|
throw e;
|
|
366
482
|
}
|
|
@@ -403,6 +519,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
403
519
|
connectToDevice(path, first) {
|
|
404
520
|
var _a;
|
|
405
521
|
return __awaiter(this, void 0, void 0, function* () {
|
|
522
|
+
this.assertRunning();
|
|
406
523
|
let device = yield this.findDevice(path);
|
|
407
524
|
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
408
525
|
yield this.closeOpenDevice(path);
|
|
@@ -415,7 +532,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
415
532
|
if (!device.opened) {
|
|
416
533
|
openingHandles.add(device);
|
|
417
534
|
try {
|
|
418
|
-
yield device.open();
|
|
535
|
+
yield this.runHandleOperation(() => device.open());
|
|
419
536
|
}
|
|
420
537
|
catch (error) {
|
|
421
538
|
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
@@ -426,9 +543,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
426
543
|
}
|
|
427
544
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
428
545
|
try {
|
|
429
|
-
yield device.reset();
|
|
546
|
+
yield this.runHandleOperation(() => device.reset());
|
|
430
547
|
}
|
|
431
548
|
catch (error) {
|
|
549
|
+
this.assertRunning();
|
|
432
550
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
433
551
|
}
|
|
434
552
|
}
|
|
@@ -445,7 +563,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
445
563
|
if (!device.opened) {
|
|
446
564
|
openingHandles.add(device);
|
|
447
565
|
try {
|
|
448
|
-
yield device.open();
|
|
566
|
+
yield this.runHandleOperation(() => device.open());
|
|
449
567
|
}
|
|
450
568
|
catch (error) {
|
|
451
569
|
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
@@ -457,12 +575,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
457
575
|
if (first ||
|
|
458
576
|
!device.configuration ||
|
|
459
577
|
device.configuration.configurationValue !== this.configurationId) {
|
|
460
|
-
yield device.selectConfiguration(this.configurationId);
|
|
578
|
+
yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
|
|
461
579
|
}
|
|
462
580
|
const endpoints = this.discoverEndpoints(device);
|
|
463
581
|
this.deviceEndpoints.set(path, endpoints);
|
|
464
582
|
try {
|
|
465
|
-
yield device.claimInterface(endpoints.interfaceNumber);
|
|
583
|
+
yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
|
|
466
584
|
}
|
|
467
585
|
catch (error) {
|
|
468
586
|
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
@@ -472,6 +590,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
472
590
|
});
|
|
473
591
|
}
|
|
474
592
|
assertHandleAvailable(device) {
|
|
593
|
+
this.assertRunning();
|
|
475
594
|
const normalOwner = normalHandleOwners.get(device);
|
|
476
595
|
if ((normalOwner && normalOwner !== this) ||
|
|
477
596
|
(identityProbeHandles.has(device) &&
|
|
@@ -485,6 +604,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
485
604
|
this.normalDevices.set(device, path);
|
|
486
605
|
}
|
|
487
606
|
releaseNormalHandles(path) {
|
|
607
|
+
if (this.stopped)
|
|
608
|
+
return;
|
|
488
609
|
for (const [device, ownedPath] of this.normalDevices) {
|
|
489
610
|
if (ownedPath === path) {
|
|
490
611
|
normalHandleOwners.delete(device);
|
|
@@ -498,6 +619,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
498
619
|
this.getErrorMessage(error).includes(USB_DEVICE_IN_USE));
|
|
499
620
|
}
|
|
500
621
|
claimIdentityProbeHandle(path, device) {
|
|
622
|
+
this.assertRunning();
|
|
501
623
|
if (device.opened ||
|
|
502
624
|
openingHandles.has(device) ||
|
|
503
625
|
identityProbeHandles.has(device) ||
|
|
@@ -508,6 +630,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
508
630
|
this.identityProbeDevices.set(path, device);
|
|
509
631
|
}
|
|
510
632
|
releaseIdentityProbeHandle(path) {
|
|
633
|
+
if (this.stopped)
|
|
634
|
+
return;
|
|
511
635
|
const device = this.identityProbeDevices.get(path);
|
|
512
636
|
if (device === null || device === void 0 ? void 0 : device.opened) {
|
|
513
637
|
this.identityProbeCleanupPaths.add(path);
|
|
@@ -521,6 +645,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
521
645
|
closeOpenDevice(path) {
|
|
522
646
|
var _a, _b, _c, _d;
|
|
523
647
|
return __awaiter(this, void 0, void 0, function* () {
|
|
648
|
+
if (this.stopped)
|
|
649
|
+
return;
|
|
524
650
|
const current = this.identityProbe
|
|
525
651
|
? this.identityProbeDevices.get(path)
|
|
526
652
|
: (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
@@ -530,14 +656,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
530
656
|
const endpoints = this.deviceEndpoints.get(path);
|
|
531
657
|
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
532
658
|
try {
|
|
533
|
-
yield current.releaseInterface(ifaceNum);
|
|
659
|
+
yield this.runHandleOperation(() => current.releaseInterface(ifaceNum));
|
|
534
660
|
}
|
|
535
661
|
catch (error) {
|
|
536
662
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
537
663
|
}
|
|
538
664
|
this.assertHandleAvailable(current);
|
|
539
665
|
try {
|
|
540
|
-
yield current.close();
|
|
666
|
+
yield this.runHandleOperation(() => current.close());
|
|
541
667
|
}
|
|
542
668
|
catch (error) {
|
|
543
669
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
@@ -551,14 +677,16 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
551
677
|
return __awaiter(this, void 0, void 0, function* () {
|
|
552
678
|
this.assertHandleAvailable(device);
|
|
553
679
|
try {
|
|
554
|
-
yield device.clearHalt(direction, endpointNumber);
|
|
680
|
+
yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
|
|
555
681
|
}
|
|
556
682
|
catch (error) {
|
|
683
|
+
this.assertRunning();
|
|
557
684
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
558
685
|
}
|
|
559
686
|
});
|
|
560
687
|
}
|
|
561
688
|
assertAcquired(path) {
|
|
689
|
+
this.assertRunning();
|
|
562
690
|
if (!this.acquiredPaths.has(path)) {
|
|
563
691
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
564
692
|
}
|
|
@@ -615,6 +743,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
615
743
|
throwWebUsbIoError(path, operation, error) {
|
|
616
744
|
var _a;
|
|
617
745
|
return __awaiter(this, void 0, void 0, function* () {
|
|
746
|
+
this.assertRunning();
|
|
618
747
|
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
619
748
|
throw error;
|
|
620
749
|
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
@@ -649,6 +778,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
649
778
|
return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
|
|
650
779
|
}
|
|
651
780
|
isRetryablePacketIoError(error) {
|
|
781
|
+
if (this.stopped)
|
|
782
|
+
return false;
|
|
652
783
|
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
653
784
|
return false;
|
|
654
785
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
@@ -668,6 +799,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
668
799
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
669
800
|
this.staleProtocolPaths.add(path);
|
|
670
801
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
802
|
+
this.assertRunning();
|
|
671
803
|
try {
|
|
672
804
|
const currentDevice = yield this.findDevice(path);
|
|
673
805
|
this.assertHandleAvailable(currentDevice);
|
|
@@ -675,16 +807,17 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
675
807
|
const endpoints = this.deviceEndpoints.get(path);
|
|
676
808
|
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
677
809
|
try {
|
|
678
|
-
yield currentDevice.releaseInterface(ifaceNum);
|
|
810
|
+
yield this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
|
|
679
811
|
}
|
|
680
812
|
catch (releaseError) {
|
|
681
813
|
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
682
814
|
}
|
|
683
815
|
this.assertHandleAvailable(currentDevice);
|
|
684
|
-
yield currentDevice.close();
|
|
816
|
+
yield this.runHandleOperation(() => currentDevice.close());
|
|
685
817
|
}
|
|
686
818
|
}
|
|
687
819
|
catch (closeError) {
|
|
820
|
+
this.assertRunning();
|
|
688
821
|
if (this.isHandleOwnershipError(closeError))
|
|
689
822
|
throw closeError;
|
|
690
823
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
@@ -728,6 +861,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
728
861
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
729
862
|
}
|
|
730
863
|
catch (reconnectError) {
|
|
864
|
+
this.assertRunning();
|
|
731
865
|
if (this.isHandleOwnershipError(reconnectError))
|
|
732
866
|
throw reconnectError;
|
|
733
867
|
lastError = reconnectError;
|
|
@@ -750,6 +884,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
750
884
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
751
885
|
this.assertHandleAvailable(device);
|
|
752
886
|
const result = yield device.transferOut(endpointOut, transferBuffer);
|
|
887
|
+
this.assertHandleAvailable(device);
|
|
753
888
|
if (result.status !== 'ok') {
|
|
754
889
|
return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
|
|
755
890
|
}
|
|
@@ -772,6 +907,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
772
907
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
773
908
|
this.assertHandleAvailable(device);
|
|
774
909
|
const result = yield device.transferIn(endpointIn, length);
|
|
910
|
+
this.assertHandleAvailable(device);
|
|
775
911
|
return this.getTransferInData(result);
|
|
776
912
|
}
|
|
777
913
|
catch (error) {
|
|
@@ -787,6 +923,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
787
923
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
788
924
|
}
|
|
789
925
|
catch (reconnectError) {
|
|
926
|
+
this.assertRunning();
|
|
790
927
|
if (this.isHandleOwnershipError(reconnectError))
|
|
791
928
|
throw reconnectError;
|
|
792
929
|
lastError = reconnectError;
|
|
@@ -808,6 +945,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
808
945
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
809
946
|
this.assertHandleAvailable(device);
|
|
810
947
|
const result = yield device.transferIn(endpointIn, length);
|
|
948
|
+
this.assertHandleAvailable(device);
|
|
811
949
|
return this.getTransferInData(result);
|
|
812
950
|
});
|
|
813
951
|
}
|
package/dist/webusb.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
20
20
|
private acquiredPaths;
|
|
21
21
|
private identityProbeDevices;
|
|
22
22
|
private normalDevices;
|
|
23
|
+
private pendingHandleOperations;
|
|
24
|
+
private stopPromise?;
|
|
23
25
|
private identityProbeCleanupPaths;
|
|
24
26
|
private probedDeviceObjects;
|
|
25
27
|
private deviceEndpoints;
|
|
@@ -35,6 +37,10 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
35
37
|
interfaceId: number;
|
|
36
38
|
constructor(identityProbe?: boolean);
|
|
37
39
|
init(logger: any, emitter?: EventEmitter): void;
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
private stopResources;
|
|
42
|
+
private assertRunning;
|
|
43
|
+
private runHandleOperation;
|
|
38
44
|
emitDeviceDisconnect(path: string, device?: USBDevice): void;
|
|
39
45
|
markProtocolStale(path: string): void;
|
|
40
46
|
configure(signedData: any): void;
|
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;AAahC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAwBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AA+CD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;AAahC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAwBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AA+CD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAuEjE,OAAO,CAAC,QAAQ,CAAC,aAAa;IAtE1C,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;IAG9D,OAAO,CAAC,wBAAwB,CAAwC;IAExE,OAAO,CAAC,mBAAmB,CAAwC;IAMnE,OAAO,CAAC,kBAAkB,CAA0B;IAGpD,OAAO,CAAC,aAAa,CAA0B;IAE/C,OAAO,CAAC,oBAAoB,CAAgC;IAE5D,OAAO,CAAC,aAAa,CAAgC;IAErD,OAAO,CAAC,uBAAuB,CAA+B;IAE9D,OAAO,CAAC,WAAW,CAAC,CAAgB;IAEpC,OAAO,CAAC,yBAAyB,CAAqB;IAStD,OAAO,CAAC,mBAAmB,CAAqC;IAGhE,OAAO,CAAC,eAAe,CAA2C;IAElE,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAMvB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;gBAEE,aAAa,UAAQ;IAWlD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAexC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAcP,aAAa;IAyD3B,OAAO,CAAC,aAAa;YAMP,kBAAkB;IAiBhC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS;IAkBrD,iBAAiB,CAAC,IAAI,EAAE,MAAM;IAQ9B,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAsB7B,kBAAkB;IAmBlB,SAAS;IAgBT,mBAAmB;IA+CnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA6GjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAItB,cAAc;IAmEtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAuB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAsElD,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;YAapB,eAAe;YAwBf,iBAAiB;IAwB/B,OAAO,CAAC,cAAc;IAOhB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IASvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,uBAAuB;YASjB,uBAAuB;YAWvB,kBAAkB;IAyChC,OAAO,CAAC,+BAA+B;IAIvC,OAAO,CAAC,wBAAwB;YAgBlB,yBAAyB;IA2CvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA6BpB,eAAe;YAsBf,mBAAmB;YA+CnB,cAAc;YAad,yBAAyB;YAKzB,yBAAyB;YAMzB,uBAAuB;YA0CvB,eAAe;YAqBf,eAAe;IAiBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAkCd,cAAc;IAkBtB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAiB1B,SAAS,CAAC,uBAAuB,IAAI,iBAAiB;IAUtD,SAAS,CAAC,sBAAsB;cAIhB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;cAIA,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC;cASN,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,SAAS,CAAC,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKrE,SAAS,CAAC,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK;IAWjF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.8-alpha.
|
|
3
|
+
"version": "1.2.8-alpha.18",
|
|
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.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.8-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.8-alpha.18",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.8-alpha.18"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.8-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.8-alpha.18",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "1f66fc960c9d775272f989aa673d8efe2174729b"
|
|
33
33
|
}
|
package/src/webusb.ts
CHANGED
|
@@ -135,6 +135,10 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
135
135
|
|
|
136
136
|
private normalDevices = new Map<USBDevice, string>();
|
|
137
137
|
|
|
138
|
+
private pendingHandleOperations = new Set<Promise<unknown>>();
|
|
139
|
+
|
|
140
|
+
private stopPromise?: Promise<void>;
|
|
141
|
+
|
|
138
142
|
private identityProbeCleanupPaths = new Set<string>();
|
|
139
143
|
|
|
140
144
|
/**
|
|
@@ -199,6 +203,96 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
199
203
|
if (!this.identityProbe) registerActiveWebUsbTransport(this, usb);
|
|
200
204
|
}
|
|
201
205
|
|
|
206
|
+
stop(): Promise<void> {
|
|
207
|
+
this.stopped = true;
|
|
208
|
+
this.acquiredPaths.clear();
|
|
209
|
+
if (activeWebUsbTransport === this) activeWebUsbTransport = undefined;
|
|
210
|
+
if (!this.stopPromise) {
|
|
211
|
+
this.stopPromise = this.stopResources().catch(error => {
|
|
212
|
+
// A later discovery can retry closure without dropping the handle guard.
|
|
213
|
+
this.stopPromise = undefined;
|
|
214
|
+
throw error;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return this.stopPromise;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private async stopResources() {
|
|
221
|
+
let cleanupError: unknown;
|
|
222
|
+
try {
|
|
223
|
+
await this.disposeProtocolV2UsbLinks('WebUSB transport stopped');
|
|
224
|
+
} catch (error) {
|
|
225
|
+
cleanupError = error;
|
|
226
|
+
}
|
|
227
|
+
// Wait for late open/reset/claim/close completions before releasing ownership.
|
|
228
|
+
// Reads are aborted by closing their handle and reject late results below.
|
|
229
|
+
await Promise.allSettled([...this.pendingHandleOperations]);
|
|
230
|
+
const handles = this.identityProbe
|
|
231
|
+
? [...this.identityProbeDevices].map(([path, device]) => [device, path] as const)
|
|
232
|
+
: [...this.normalDevices];
|
|
233
|
+
for (const [device, path] of handles) {
|
|
234
|
+
if (!this.identityProbe && normalHandleOwners.get(device) !== this) {
|
|
235
|
+
this.normalDevices.delete(device);
|
|
236
|
+
} else {
|
|
237
|
+
try {
|
|
238
|
+
if (device.opened) {
|
|
239
|
+
const iface = this.deviceEndpoints.get(path)?.interfaceNumber ?? this.interfaceId;
|
|
240
|
+
try {
|
|
241
|
+
await device.releaseInterface(iface);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
this.Log?.debug('[WebUsbTransport] stop releaseInterface failed:', error);
|
|
244
|
+
}
|
|
245
|
+
await device.close();
|
|
246
|
+
if (device.opened) throw new Error('WebUSB handle remains open after stop');
|
|
247
|
+
}
|
|
248
|
+
if (this.identityProbe) {
|
|
249
|
+
identityProbeHandles.delete(device);
|
|
250
|
+
this.identityProbeDevices.delete(path);
|
|
251
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
252
|
+
} else {
|
|
253
|
+
normalHandleOwners.delete(device);
|
|
254
|
+
this.normalDevices.delete(device);
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
cleanupError ??= error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.deviceList = [];
|
|
262
|
+
this.deviceProtocol.clear();
|
|
263
|
+
this.confirmedDeviceProtocols.clear();
|
|
264
|
+
this.deviceProtocolHints.clear();
|
|
265
|
+
this.staleProtocolPaths.clear();
|
|
266
|
+
this.probedDeviceObjects.clear();
|
|
267
|
+
if (!this.normalDevices.size && !this.identityProbeDevices.size) {
|
|
268
|
+
this.deviceEndpoints.clear();
|
|
269
|
+
}
|
|
270
|
+
if (cleanupError) {
|
|
271
|
+
throw cleanupError instanceof Error
|
|
272
|
+
? cleanupError
|
|
273
|
+
: new Error(this.getErrorMessage(cleanupError));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private assertRunning() {
|
|
278
|
+
if (this.stopped) {
|
|
279
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'WebUSB transport is stopped');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private async runHandleOperation<T>(operation: () => Promise<T>): Promise<T> {
|
|
284
|
+
this.assertRunning();
|
|
285
|
+
const pending = Promise.resolve(operation());
|
|
286
|
+
this.pendingHandleOperations.add(pending);
|
|
287
|
+
try {
|
|
288
|
+
const result = await pending;
|
|
289
|
+
this.assertRunning();
|
|
290
|
+
return result;
|
|
291
|
+
} finally {
|
|
292
|
+
this.pendingHandleOperations.delete(pending);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
202
296
|
/**
|
|
203
297
|
* Announce that a USB device left. Called from the module-scoped disconnect
|
|
204
298
|
* listener, which is why it is public rather than inlined.
|
|
@@ -296,9 +390,21 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
296
390
|
* Get list of connected devices
|
|
297
391
|
*/
|
|
298
392
|
async getConnectedDevices() {
|
|
393
|
+
this.assertRunning();
|
|
299
394
|
if (!this.usb) return [];
|
|
300
395
|
|
|
301
396
|
const devices = await this.usb.getDevices();
|
|
397
|
+
this.assertRunning();
|
|
398
|
+
for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
|
|
399
|
+
if (owner?.stopped) {
|
|
400
|
+
try {
|
|
401
|
+
await owner.stop();
|
|
402
|
+
} catch (error) {
|
|
403
|
+
this.Log?.debug('[WebUsbTransport] stopped owner cleanup failed:', error);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
this.assertRunning();
|
|
302
408
|
const onekeyDevices = devices.filter(dev => {
|
|
303
409
|
const isOneKey = ONEKEY_WEBUSB_FILTER.some(
|
|
304
410
|
(desc: { vendorId: number; productId: number }) =>
|
|
@@ -331,6 +437,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
331
437
|
* Acquire device control
|
|
332
438
|
*/
|
|
333
439
|
async acquire(input: AcquireInput) {
|
|
440
|
+
this.assertRunning();
|
|
334
441
|
if (!input.path) return;
|
|
335
442
|
if (this.identityProbe) {
|
|
336
443
|
await this.getConnectedDevices();
|
|
@@ -410,12 +517,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
410
517
|
input.expectedProtocol,
|
|
411
518
|
protocolHint
|
|
412
519
|
);
|
|
520
|
+
this.assertRunning();
|
|
413
521
|
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
414
522
|
const probedDevice = this.deviceList.find(d => d.path === input.path)?.device;
|
|
415
523
|
if (probedDevice) {
|
|
416
524
|
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
417
525
|
}
|
|
418
526
|
}
|
|
527
|
+
this.assertRunning();
|
|
419
528
|
this.acquiredPaths.add(input.path);
|
|
420
529
|
return await Promise.resolve(input.path);
|
|
421
530
|
} catch (e) {
|
|
@@ -548,9 +657,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
548
657
|
async connect(path: string, first: boolean) {
|
|
549
658
|
const maxRetries = 5;
|
|
550
659
|
for (let i = 0; i < maxRetries; i++) {
|
|
660
|
+
this.assertRunning();
|
|
551
661
|
try {
|
|
552
662
|
return await this.connectToDevice(path, first);
|
|
553
663
|
} catch (e) {
|
|
664
|
+
this.assertRunning();
|
|
554
665
|
if (e instanceof HardwareError && e.errorCode === HardwareErrorCode.BridgeNeedsPermission) {
|
|
555
666
|
throw e;
|
|
556
667
|
}
|
|
@@ -600,6 +711,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
600
711
|
* Discovers interface/endpoint numbers from USB descriptors on first connection.
|
|
601
712
|
*/
|
|
602
713
|
async connectToDevice(path: string, first: boolean) {
|
|
714
|
+
this.assertRunning();
|
|
603
715
|
let device: USBDevice = await this.findDevice(path);
|
|
604
716
|
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
605
717
|
await this.closeOpenDevice(path);
|
|
@@ -611,7 +723,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
611
723
|
if (!device.opened) {
|
|
612
724
|
openingHandles.add(device);
|
|
613
725
|
try {
|
|
614
|
-
await device.open();
|
|
726
|
+
await this.runHandleOperation(() => device.open());
|
|
615
727
|
} catch (error) {
|
|
616
728
|
return await this.throwWebUsbIoError(path, 'open', error);
|
|
617
729
|
} finally {
|
|
@@ -622,8 +734,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
622
734
|
// state as in the legacy transport; probing and V2 recovery still reset.
|
|
623
735
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
624
736
|
try {
|
|
625
|
-
await device.reset();
|
|
737
|
+
await this.runHandleOperation(() => device.reset());
|
|
626
738
|
} catch (error) {
|
|
739
|
+
this.assertRunning();
|
|
627
740
|
this.Log?.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
628
741
|
}
|
|
629
742
|
}
|
|
@@ -639,7 +752,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
639
752
|
if (!device.opened) {
|
|
640
753
|
openingHandles.add(device);
|
|
641
754
|
try {
|
|
642
|
-
await device.open();
|
|
755
|
+
await this.runHandleOperation(() => device.open());
|
|
643
756
|
} catch (error) {
|
|
644
757
|
return await this.throwWebUsbIoError(path, 'open', error);
|
|
645
758
|
} finally {
|
|
@@ -652,14 +765,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
652
765
|
!device.configuration ||
|
|
653
766
|
device.configuration.configurationValue !== this.configurationId
|
|
654
767
|
) {
|
|
655
|
-
await device.selectConfiguration(this.configurationId);
|
|
768
|
+
await this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
|
|
656
769
|
}
|
|
657
770
|
|
|
658
771
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
659
772
|
const endpoints = this.discoverEndpoints(device);
|
|
660
773
|
this.deviceEndpoints.set(path, endpoints);
|
|
661
774
|
try {
|
|
662
|
-
await device.claimInterface(endpoints.interfaceNumber);
|
|
775
|
+
await this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
|
|
663
776
|
} catch (error) {
|
|
664
777
|
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
665
778
|
}
|
|
@@ -668,6 +781,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
668
781
|
}
|
|
669
782
|
|
|
670
783
|
private assertHandleAvailable(device: USBDevice) {
|
|
784
|
+
this.assertRunning();
|
|
671
785
|
const normalOwner = normalHandleOwners.get(device);
|
|
672
786
|
if (
|
|
673
787
|
(normalOwner && normalOwner !== this) ||
|
|
@@ -685,6 +799,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
685
799
|
}
|
|
686
800
|
|
|
687
801
|
private releaseNormalHandles(path: string) {
|
|
802
|
+
if (this.stopped) return;
|
|
688
803
|
for (const [device, ownedPath] of this.normalDevices) {
|
|
689
804
|
if (ownedPath === path) {
|
|
690
805
|
normalHandleOwners.delete(device);
|
|
@@ -702,6 +817,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
702
817
|
}
|
|
703
818
|
|
|
704
819
|
private claimIdentityProbeHandle(path: string, device: USBDevice) {
|
|
820
|
+
this.assertRunning();
|
|
705
821
|
if (
|
|
706
822
|
device.opened ||
|
|
707
823
|
openingHandles.has(device) ||
|
|
@@ -715,6 +831,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
715
831
|
}
|
|
716
832
|
|
|
717
833
|
private releaseIdentityProbeHandle(path: string) {
|
|
834
|
+
if (this.stopped) return;
|
|
718
835
|
const device = this.identityProbeDevices.get(path);
|
|
719
836
|
// A failed native close must not make an open probe handle available to other callers.
|
|
720
837
|
if (device?.opened) {
|
|
@@ -727,6 +844,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
727
844
|
}
|
|
728
845
|
|
|
729
846
|
private async closeOpenDevice(path: string) {
|
|
847
|
+
if (this.stopped) return;
|
|
730
848
|
const current = this.identityProbe
|
|
731
849
|
? this.identityProbeDevices.get(path)
|
|
732
850
|
: this.deviceList.find(device => device.path === path)?.device;
|
|
@@ -736,13 +854,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
736
854
|
const endpoints = this.deviceEndpoints.get(path);
|
|
737
855
|
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
738
856
|
try {
|
|
739
|
-
await current.releaseInterface(ifaceNum);
|
|
857
|
+
await this.runHandleOperation(() => current.releaseInterface(ifaceNum));
|
|
740
858
|
} catch (error) {
|
|
741
859
|
this.Log?.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
742
860
|
}
|
|
743
861
|
this.assertHandleAvailable(current);
|
|
744
862
|
try {
|
|
745
|
-
await current.close();
|
|
863
|
+
await this.runHandleOperation(() => current.close());
|
|
746
864
|
} catch (error) {
|
|
747
865
|
this.Log?.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
748
866
|
if (this.identityProbe) throw error;
|
|
@@ -756,8 +874,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
756
874
|
) {
|
|
757
875
|
this.assertHandleAvailable(device);
|
|
758
876
|
try {
|
|
759
|
-
await device.clearHalt(direction, endpointNumber);
|
|
877
|
+
await this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
|
|
760
878
|
} catch (error) {
|
|
879
|
+
this.assertRunning();
|
|
761
880
|
this.Log?.debug(
|
|
762
881
|
`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`,
|
|
763
882
|
error
|
|
@@ -773,6 +892,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
773
892
|
* protocol entry produced the same fail-fast).
|
|
774
893
|
*/
|
|
775
894
|
private assertAcquired(path: string) {
|
|
895
|
+
this.assertRunning();
|
|
776
896
|
if (!this.acquiredPaths.has(path)) {
|
|
777
897
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
778
898
|
}
|
|
@@ -830,6 +950,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
830
950
|
operation: WebUsbOperation,
|
|
831
951
|
error: unknown
|
|
832
952
|
): Promise<never> {
|
|
953
|
+
this.assertRunning();
|
|
833
954
|
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error)) throw error;
|
|
834
955
|
if (error instanceof ProtocolV2LinkError && error.code !== 'io') throw error;
|
|
835
956
|
|
|
@@ -870,6 +991,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
870
991
|
}
|
|
871
992
|
|
|
872
993
|
private isRetryablePacketIoError(error: unknown) {
|
|
994
|
+
if (this.stopped) return false;
|
|
873
995
|
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error)) return false;
|
|
874
996
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
875
997
|
if (this.isWebUsbPermissionDeniedMessage(message)) return false;
|
|
@@ -901,6 +1023,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
901
1023
|
// USB disconnect event itself was missed.
|
|
902
1024
|
this.staleProtocolPaths.add(path);
|
|
903
1025
|
await wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
1026
|
+
this.assertRunning();
|
|
904
1027
|
|
|
905
1028
|
try {
|
|
906
1029
|
const currentDevice = await this.findDevice(path);
|
|
@@ -909,14 +1032,15 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
909
1032
|
const endpoints = this.deviceEndpoints.get(path);
|
|
910
1033
|
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
911
1034
|
try {
|
|
912
|
-
await currentDevice.releaseInterface(ifaceNum);
|
|
1035
|
+
await this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
|
|
913
1036
|
} catch (releaseError) {
|
|
914
1037
|
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
915
1038
|
}
|
|
916
1039
|
this.assertHandleAvailable(currentDevice);
|
|
917
|
-
await currentDevice.close();
|
|
1040
|
+
await this.runHandleOperation(() => currentDevice.close());
|
|
918
1041
|
}
|
|
919
1042
|
} catch (closeError) {
|
|
1043
|
+
this.assertRunning();
|
|
920
1044
|
if (this.isHandleOwnershipError(closeError)) throw closeError;
|
|
921
1045
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
922
1046
|
}
|
|
@@ -959,6 +1083,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
959
1083
|
try {
|
|
960
1084
|
await this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
961
1085
|
} catch (reconnectError) {
|
|
1086
|
+
this.assertRunning();
|
|
962
1087
|
if (this.isHandleOwnershipError(reconnectError)) throw reconnectError;
|
|
963
1088
|
lastError = reconnectError;
|
|
964
1089
|
this.Log.debug(
|
|
@@ -984,6 +1109,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
984
1109
|
);
|
|
985
1110
|
this.assertHandleAvailable(device);
|
|
986
1111
|
const result = await device.transferOut(endpointOut, transferBuffer);
|
|
1112
|
+
this.assertHandleAvailable(device);
|
|
987
1113
|
if (result.status !== 'ok') {
|
|
988
1114
|
return this.throwWebUsbIoError(
|
|
989
1115
|
path,
|
|
@@ -1012,6 +1138,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
1012
1138
|
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
1013
1139
|
this.assertHandleAvailable(device);
|
|
1014
1140
|
const result = await device.transferIn(endpointIn, length);
|
|
1141
|
+
this.assertHandleAvailable(device);
|
|
1015
1142
|
return this.getTransferInData(result);
|
|
1016
1143
|
} catch (error) {
|
|
1017
1144
|
lastError = error;
|
|
@@ -1025,6 +1152,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
1025
1152
|
try {
|
|
1026
1153
|
await this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
1027
1154
|
} catch (reconnectError) {
|
|
1155
|
+
this.assertRunning();
|
|
1028
1156
|
if (this.isHandleOwnershipError(reconnectError)) throw reconnectError;
|
|
1029
1157
|
lastError = reconnectError;
|
|
1030
1158
|
this.Log.debug(
|
|
@@ -1047,6 +1175,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
1047
1175
|
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
1048
1176
|
this.assertHandleAvailable(device);
|
|
1049
1177
|
const result = await device.transferIn(endpointIn, length);
|
|
1178
|
+
this.assertHandleAvailable(device);
|
|
1050
1179
|
return this.getTransferInData(result);
|
|
1051
1180
|
}
|
|
1052
1181
|
|