@onekeyfe/hd-transport-web-device 1.2.0-alpha.24 → 1.2.0-alpha.26
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-protocol-v2-timeout.test.ts +134 -42
- package/dist/index.d.ts +12 -9
- package/dist/index.js +81 -95
- package/dist/webusb.d.ts +13 -7
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/webusb.ts +95 -116
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import transport, {
|
|
2
|
+
PROTOCOL_V2_CHANNEL_USB,
|
|
2
3
|
ProtocolV2,
|
|
3
|
-
ProtocolV2FrameAssembler,
|
|
4
4
|
ProtocolV2LinkError,
|
|
5
5
|
} from '@onekeyfe/hd-transport';
|
|
6
6
|
|
|
@@ -20,6 +20,30 @@ const schema = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
23
|
+
test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
|
|
24
|
+
const webusb = new WebUsbTransport() as any;
|
|
25
|
+
webusb.invalidateAllProtocolV2UsbLinks = jest.fn().mockResolvedValue(undefined);
|
|
26
|
+
const schemaSource = JSON.stringify(schema);
|
|
27
|
+
|
|
28
|
+
webusb.configureProtocolV2(schemaSource);
|
|
29
|
+
webusb.configureProtocolV2(schemaSource);
|
|
30
|
+
|
|
31
|
+
expect(webusb.invalidateAllProtocolV2UsbLinks).not.toHaveBeenCalled();
|
|
32
|
+
|
|
33
|
+
webusb.configureProtocolV2(
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
...schema,
|
|
36
|
+
nested: {
|
|
37
|
+
...schema.nested,
|
|
38
|
+
Failure: { fields: { message: { type: 'string', id: 1 } } },
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
expect(webusb.invalidateAllProtocolV2UsbLinks).toHaveBeenCalledWith(
|
|
43
|
+
'Protocol V2 schema reconfigured'
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
23
47
|
test('resets the connection between a failed V1 probe and the V2 probe', async () => {
|
|
24
48
|
const webusb = new WebUsbTransport() as any;
|
|
25
49
|
const path = 'pro2-webusb';
|
|
@@ -80,20 +104,72 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
80
104
|
const path = 'pro2-webusb';
|
|
81
105
|
webusb.messages = transport.parseConfigure(schema);
|
|
82
106
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
83
|
-
webusb.
|
|
84
|
-
webusb.
|
|
85
|
-
webusb.
|
|
86
|
-
webusb.resetConnectionAfterProbe = jest.fn()
|
|
87
|
-
|
|
88
|
-
webusb.protocolV2Assemblers.get(path)?.reset();
|
|
89
|
-
});
|
|
107
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
108
|
+
webusb.readProtocolV2UsbPacket = jest.fn(() => new Promise<void>(() => {}));
|
|
109
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
110
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
111
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
90
112
|
|
|
91
113
|
await expect(
|
|
92
114
|
webusb.callProtocolV2(path, 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
|
|
93
115
|
).rejects.toThrow('timeout');
|
|
94
116
|
|
|
95
|
-
expect(webusb.
|
|
96
|
-
|
|
117
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
118
|
+
path,
|
|
119
|
+
expect.stringContaining('timeout')
|
|
120
|
+
);
|
|
121
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('does not reconnect inside a Protocol V2 frame read after a USB I/O failure', async () => {
|
|
125
|
+
const webusb = new WebUsbTransport() as any;
|
|
126
|
+
const path = 'pro2-webusb';
|
|
127
|
+
webusb.messages = transport.parseConfigure(schema);
|
|
128
|
+
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
129
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
130
|
+
webusb.readProtocolV2UsbPacket = jest
|
|
131
|
+
.fn()
|
|
132
|
+
.mockRejectedValue(new Error('NetworkError: transferIn device disconnected'));
|
|
133
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
134
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
135
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
136
|
+
|
|
137
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })).rejects.toThrow(
|
|
138
|
+
'NetworkError'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
142
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
143
|
+
path,
|
|
144
|
+
expect.stringContaining('NetworkError')
|
|
145
|
+
);
|
|
146
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('rejects an active Protocol V2 read without reconnecting after release', async () => {
|
|
150
|
+
const webusb = new WebUsbTransport() as any;
|
|
151
|
+
const path = 'pro2-webusb';
|
|
152
|
+
let markReadStarted: () => void = () => undefined;
|
|
153
|
+
const readStarted = new Promise<void>(resolve => {
|
|
154
|
+
markReadStarted = resolve;
|
|
155
|
+
});
|
|
156
|
+
webusb.messages = transport.parseConfigure(schema);
|
|
157
|
+
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
158
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
159
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
|
|
160
|
+
markReadStarted();
|
|
161
|
+
return new Promise<void>(() => {});
|
|
162
|
+
});
|
|
163
|
+
webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
|
|
164
|
+
webusb.connect = jest.fn();
|
|
165
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
166
|
+
|
|
167
|
+
const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
|
|
168
|
+
await readStarted;
|
|
169
|
+
await webusb.release(path);
|
|
170
|
+
|
|
171
|
+
await expect(call).rejects.toThrow('WebUSB transport released');
|
|
172
|
+
expect(webusb.connect).not.toHaveBeenCalled();
|
|
97
173
|
});
|
|
98
174
|
|
|
99
175
|
test.each(['router', 'packet-source', 'ack-sequence', 'response-sequence', 'frame'] as const)(
|
|
@@ -103,32 +179,34 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
103
179
|
const path = 'pro2-webusb';
|
|
104
180
|
webusb.messages = transport.parseConfigure(schema);
|
|
105
181
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
106
|
-
webusb.
|
|
107
|
-
webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
|
|
182
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
108
183
|
const recoveredResponse = ProtocolV2.encodeFrame(
|
|
109
184
|
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
110
185
|
'Success',
|
|
111
186
|
{ message: 'recovered' },
|
|
112
187
|
{ seq: 1 }
|
|
113
188
|
);
|
|
114
|
-
webusb.
|
|
189
|
+
webusb.readProtocolV2UsbPacket = jest
|
|
115
190
|
.fn()
|
|
116
191
|
.mockRejectedValueOnce(
|
|
117
192
|
new ProtocolV2LinkError(code, `Protocol V2 ${code} validation failed`)
|
|
118
193
|
)
|
|
119
194
|
.mockResolvedValue(recoveredResponse);
|
|
120
|
-
webusb.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
195
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
196
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
197
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
124
198
|
|
|
125
199
|
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'mismatch' })).rejects.toThrow(
|
|
126
200
|
`${code} validation failed`
|
|
127
201
|
);
|
|
128
202
|
|
|
129
|
-
expect(webusb.
|
|
130
|
-
|
|
203
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
204
|
+
path,
|
|
205
|
+
expect.stringContaining(`${code} validation failed`)
|
|
206
|
+
);
|
|
207
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
131
208
|
|
|
209
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test reconnect');
|
|
132
210
|
await expect(
|
|
133
211
|
webusb.callProtocolV2(path, 'Ping', { message: 'after-reset' })
|
|
134
212
|
).resolves.toMatchObject({
|
|
@@ -141,39 +219,53 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
141
219
|
test('does not discard buffered Protocol V2 frames before each call', async () => {
|
|
142
220
|
const webusb = new WebUsbTransport() as any;
|
|
143
221
|
const path = 'pro2-webusb';
|
|
144
|
-
const assembler = new ProtocolV2FrameAssembler();
|
|
145
|
-
const reset = jest.spyOn(assembler, 'reset');
|
|
146
|
-
let responseSequence = 0;
|
|
147
222
|
webusb.messages = transport.parseConfigure(schema);
|
|
148
223
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
149
|
-
webusb.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
224
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
225
|
+
const firstResponse = ProtocolV2.encodeFrame(
|
|
226
|
+
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
227
|
+
'Success',
|
|
228
|
+
{ message: 'first' },
|
|
229
|
+
{ router: PROTOCOL_V2_CHANNEL_USB, seq: 1 }
|
|
230
|
+
);
|
|
231
|
+
const secondResponse = ProtocolV2.encodeFrame(
|
|
232
|
+
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
233
|
+
'Success',
|
|
234
|
+
{ message: 'second' },
|
|
235
|
+
{ router: PROTOCOL_V2_CHANNEL_USB, seq: 2 }
|
|
236
|
+
);
|
|
237
|
+
const coalescedResponses = new Uint8Array(firstResponse.length + secondResponse.length);
|
|
238
|
+
coalescedResponses.set(firstResponse);
|
|
239
|
+
coalescedResponses.set(secondResponse, firstResponse.length);
|
|
240
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockResolvedValue(coalescedResponses);
|
|
241
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
242
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
161
243
|
|
|
162
|
-
await webusb.callProtocolV2(path, 'Ping', { message: 'first' })
|
|
163
|
-
|
|
244
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'first' })).resolves.toMatchObject({
|
|
245
|
+
type: 'Success',
|
|
246
|
+
message: { message: 'first' },
|
|
247
|
+
});
|
|
248
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'second' })).resolves.toMatchObject(
|
|
249
|
+
{
|
|
250
|
+
type: 'Success',
|
|
251
|
+
message: { message: 'second' },
|
|
252
|
+
}
|
|
253
|
+
);
|
|
164
254
|
|
|
165
|
-
expect(
|
|
255
|
+
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
166
256
|
});
|
|
167
257
|
|
|
168
258
|
test('keeps queued Protocol V2 read timeouts scoped to each call', async () => {
|
|
169
259
|
const webusb = new WebUsbTransport() as any;
|
|
170
260
|
const path = 'pro2-webusb';
|
|
171
261
|
let responseSequence = 0;
|
|
262
|
+
const readTimeouts: number[] = [];
|
|
172
263
|
webusb.messages = transport.parseConfigure(schema);
|
|
173
264
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
174
|
-
webusb.
|
|
175
|
-
webusb.
|
|
265
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
266
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation((_path, context) => {
|
|
176
267
|
responseSequence += 1;
|
|
268
|
+
readTimeouts.push(context.timeoutMs);
|
|
177
269
|
return Promise.resolve(
|
|
178
270
|
ProtocolV2.encodeFrame(
|
|
179
271
|
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
@@ -183,14 +275,14 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
183
275
|
)
|
|
184
276
|
);
|
|
185
277
|
});
|
|
278
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
279
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
186
280
|
|
|
187
281
|
await Promise.all([
|
|
188
282
|
webusb.callProtocolV2(path, 'Ping', { message: 'long' }, { timeoutMs: 1_000 }),
|
|
189
283
|
webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
|
|
190
284
|
]);
|
|
191
285
|
|
|
192
|
-
expect(
|
|
193
|
-
webusb.receiveProtocolV2Frame.mock.calls.map(([, timeoutMs]: unknown[]) => timeoutMs)
|
|
194
|
-
).toEqual([1_000, 25]);
|
|
286
|
+
expect(readTimeouts).toEqual([1_000, 25]);
|
|
195
287
|
});
|
|
196
288
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _onekeyfe_hd_transport from '@onekeyfe/hd-transport';
|
|
2
|
-
import _onekeyfe_hd_transport__default, { AcquireInput, TransportCallOptions, ProtocolType, OneKeyDeviceInfoBase, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
|
|
2
|
+
import _onekeyfe_hd_transport__default, { ProtocolV2UsbTransportBase, AcquireInput, TransportCallOptions, ProtocolV2Schemas, ProtocolV2CallContext, ProtocolType, OneKeyDeviceInfoBase, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
|
|
3
3
|
import { Deferred } from '@onekeyfe/hd-shared';
|
|
4
4
|
import { DesktopAPI } from '@onekeyfe/hd-transport-electron';
|
|
5
5
|
import EventEmitter from 'events';
|
|
@@ -12,19 +12,14 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
|
|
|
12
12
|
device: USBDevice;
|
|
13
13
|
protocolType?: ProtocolType;
|
|
14
14
|
}
|
|
15
|
-
declare class WebUsbTransport {
|
|
15
|
+
declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
16
16
|
messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
|
|
17
17
|
/** Protobuf schema for Protocol V2 transports. */
|
|
18
18
|
messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
|
|
19
|
+
private protocolV2SchemaSource;
|
|
19
20
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
20
21
|
private deviceProtocol;
|
|
21
22
|
private deviceProtocolHints;
|
|
22
|
-
/** Per-device Protocol V2 assembler that retains extra frames from one read. */
|
|
23
|
-
private protocolV2Assemblers;
|
|
24
|
-
/** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
|
|
25
|
-
private protocolV2Sessions;
|
|
26
|
-
/** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
|
|
27
|
-
private protocolV2Sequences;
|
|
28
23
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
29
24
|
private deviceEndpoints;
|
|
30
25
|
/**
|
|
@@ -46,6 +41,7 @@ declare class WebUsbTransport {
|
|
|
46
41
|
configurationId: number;
|
|
47
42
|
endpointId: number;
|
|
48
43
|
interfaceId: number;
|
|
44
|
+
constructor();
|
|
49
45
|
/**
|
|
50
46
|
* Initialize WebUSB transport
|
|
51
47
|
*/
|
|
@@ -120,6 +116,7 @@ declare class WebUsbTransport {
|
|
|
120
116
|
private transferOutWithRetry;
|
|
121
117
|
private transferOutOnce;
|
|
122
118
|
private transferInWithRetry;
|
|
119
|
+
private transferInOnce;
|
|
123
120
|
private resetConnectionAfterProbe;
|
|
124
121
|
private withProtocolReadTimeout;
|
|
125
122
|
private probeProtocolV1;
|
|
@@ -136,7 +133,6 @@ declare class WebUsbTransport {
|
|
|
136
133
|
* Decoding: Protocol V2 frame → messageTypeId + pb bytes → protobuf message
|
|
137
134
|
*/
|
|
138
135
|
private callProtocolV2;
|
|
139
|
-
private receiveProtocolV2Frame;
|
|
140
136
|
/**
|
|
141
137
|
* Receive data from device
|
|
142
138
|
*/
|
|
@@ -145,6 +141,13 @@ declare class WebUsbTransport {
|
|
|
145
141
|
* Release device
|
|
146
142
|
*/
|
|
147
143
|
release(path: string): Promise<void>;
|
|
144
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
|
|
145
|
+
protected getProtocolV2UsbLogger(): any;
|
|
146
|
+
protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
|
|
147
|
+
protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
|
|
148
|
+
protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
|
|
149
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
|
|
150
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
|
|
148
151
|
/**
|
|
149
152
|
* Expose the detected protocol type for a given device path.
|
|
150
153
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|
package/dist/index.js
CHANGED
|
@@ -64,13 +64,15 @@ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
|
64
64
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
65
65
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
66
66
|
}
|
|
67
|
-
class WebUsbTransport {
|
|
67
|
+
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
68
68
|
constructor() {
|
|
69
|
+
super({
|
|
70
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
71
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
72
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
73
|
+
});
|
|
69
74
|
this.deviceProtocol = new Map();
|
|
70
75
|
this.deviceProtocolHints = new Map();
|
|
71
|
-
this.protocolV2Assemblers = new Map();
|
|
72
|
-
this.protocolV2Sessions = new Map();
|
|
73
|
-
this.protocolV2Sequences = new Map();
|
|
74
76
|
this.deviceEndpoints = new Map();
|
|
75
77
|
this.mockSerialPaths = new WeakMap();
|
|
76
78
|
this.mockSerialCounter = 0;
|
|
@@ -96,8 +98,18 @@ class WebUsbTransport {
|
|
|
96
98
|
this.messages = messages;
|
|
97
99
|
}
|
|
98
100
|
configureProtocolV2(signedData) {
|
|
101
|
+
var _a;
|
|
102
|
+
const schemaSource = typeof signedData === 'string'
|
|
103
|
+
? signedData
|
|
104
|
+
: (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
|
|
105
|
+
if (schemaSource === this.protocolV2SchemaSource)
|
|
106
|
+
return;
|
|
107
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
99
108
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
100
|
-
this.
|
|
109
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
110
|
+
if (!hadProtocolV2Schema)
|
|
111
|
+
return;
|
|
112
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] schema link cleanup failed:', error); });
|
|
101
113
|
}
|
|
102
114
|
promptDeviceAccess() {
|
|
103
115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -163,6 +175,7 @@ class WebUsbTransport {
|
|
|
163
175
|
if (!input.path)
|
|
164
176
|
return;
|
|
165
177
|
try {
|
|
178
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
166
179
|
yield this.closeOpenDevice(input.path);
|
|
167
180
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
168
181
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
@@ -289,7 +302,7 @@ class WebUsbTransport {
|
|
|
289
302
|
};
|
|
290
303
|
}
|
|
291
304
|
connectToDevice(path, first) {
|
|
292
|
-
var _a
|
|
305
|
+
var _a;
|
|
293
306
|
return __awaiter(this, void 0, void 0, function* () {
|
|
294
307
|
let device = yield this.findDevice(path);
|
|
295
308
|
if (!device.opened) {
|
|
@@ -313,33 +326,30 @@ class WebUsbTransport {
|
|
|
313
326
|
}
|
|
314
327
|
const endpoints = this.discoverEndpoints(device);
|
|
315
328
|
this.deviceEndpoints.set(path, endpoints);
|
|
316
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
317
|
-
this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
318
329
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
319
330
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
320
331
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
321
332
|
});
|
|
322
333
|
}
|
|
323
334
|
closeOpenDevice(path) {
|
|
324
|
-
var _a, _b, _c, _d
|
|
335
|
+
var _a, _b, _c, _d;
|
|
325
336
|
return __awaiter(this, void 0, void 0, function* () {
|
|
326
|
-
(_a = this.
|
|
327
|
-
const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
337
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
328
338
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
329
339
|
return;
|
|
330
340
|
const endpoints = this.deviceEndpoints.get(path);
|
|
331
|
-
const ifaceNum = (
|
|
341
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
332
342
|
try {
|
|
333
343
|
yield current.releaseInterface(ifaceNum);
|
|
334
344
|
}
|
|
335
345
|
catch (error) {
|
|
336
|
-
(
|
|
346
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
337
347
|
}
|
|
338
348
|
try {
|
|
339
349
|
yield current.close();
|
|
340
350
|
}
|
|
341
351
|
catch (error) {
|
|
342
|
-
(
|
|
352
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
343
353
|
}
|
|
344
354
|
});
|
|
345
355
|
}
|
|
@@ -501,16 +511,28 @@ class WebUsbTransport {
|
|
|
501
511
|
throw lastError;
|
|
502
512
|
});
|
|
503
513
|
}
|
|
514
|
+
transferInOnce(path, length) {
|
|
515
|
+
var _a;
|
|
516
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
517
|
+
const device = yield this.findDevice(path);
|
|
518
|
+
if (!device.opened) {
|
|
519
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
520
|
+
}
|
|
521
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
522
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
523
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
524
|
+
return this.getTransferInData(result);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
504
527
|
resetConnectionAfterProbe(path) {
|
|
505
|
-
var _a
|
|
528
|
+
var _a;
|
|
506
529
|
return __awaiter(this, void 0, void 0, function* () {
|
|
507
|
-
|
|
508
|
-
this.protocolV2Sessions.delete(path);
|
|
530
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
509
531
|
try {
|
|
510
532
|
const device = yield this.findDevice(path);
|
|
511
533
|
if (device.opened) {
|
|
512
534
|
const endpoints = this.deviceEndpoints.get(path);
|
|
513
|
-
const ifaceNum = (
|
|
535
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
514
536
|
try {
|
|
515
537
|
yield device.releaseInterface(ifaceNum);
|
|
516
538
|
}
|
|
@@ -584,7 +606,7 @@ class WebUsbTransport {
|
|
|
584
606
|
return false;
|
|
585
607
|
}
|
|
586
608
|
return transport.probeProtocolV2({
|
|
587
|
-
call: (name, data, options) => this.callProtocolV2(path, name, data, options
|
|
609
|
+
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
588
610
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
589
611
|
logger: this.Log,
|
|
590
612
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
@@ -634,74 +656,9 @@ class WebUsbTransport {
|
|
|
634
656
|
return check$1.call(jsonData);
|
|
635
657
|
});
|
|
636
658
|
}
|
|
637
|
-
callProtocolV2(path, name, data, options
|
|
659
|
+
callProtocolV2(path, name, data, options) {
|
|
638
660
|
return __awaiter(this, void 0, void 0, function* () {
|
|
639
|
-
|
|
640
|
-
if (!this.messagesV2) {
|
|
641
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
642
|
-
}
|
|
643
|
-
if (!protocolV1Messages) {
|
|
644
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
645
|
-
}
|
|
646
|
-
let session = this.protocolV2Sessions.get(path);
|
|
647
|
-
if (!session) {
|
|
648
|
-
let sequenceCursor = this.protocolV2Sequences.get(path);
|
|
649
|
-
if (!sequenceCursor) {
|
|
650
|
-
sequenceCursor = new transport.ProtocolV2SequenceCursor();
|
|
651
|
-
this.protocolV2Sequences.set(path, sequenceCursor);
|
|
652
|
-
}
|
|
653
|
-
session = new transport.ProtocolV2Session({
|
|
654
|
-
schemas: {
|
|
655
|
-
protocolV1: protocolV1Messages,
|
|
656
|
-
protocolV2: this.messagesV2,
|
|
657
|
-
},
|
|
658
|
-
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
659
|
-
sequenceCursor,
|
|
660
|
-
writeFrame: (frame) => this.transferOutOnce(path, frame),
|
|
661
|
-
readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
|
|
662
|
-
logger: this.Log,
|
|
663
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
664
|
-
createTimeoutError: (messageName, timeoutMs) => new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
665
|
-
});
|
|
666
|
-
this.protocolV2Sessions.set(path, session);
|
|
667
|
-
}
|
|
668
|
-
try {
|
|
669
|
-
return yield session.call(name, data, options);
|
|
670
|
-
}
|
|
671
|
-
catch (error) {
|
|
672
|
-
if (resetOnError && (transport.isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
|
|
673
|
-
try {
|
|
674
|
-
yield this.resetConnectionAfterProbe(path);
|
|
675
|
-
}
|
|
676
|
-
catch (resetError) {
|
|
677
|
-
this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
throw error;
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
receiveProtocolV2Frame(path, timeoutMs) {
|
|
685
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
686
|
-
let assembler = this.protocolV2Assemblers.get(path);
|
|
687
|
-
if (!assembler) {
|
|
688
|
-
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
689
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
690
|
-
}
|
|
691
|
-
let frame = assembler.push(new Uint8Array(0));
|
|
692
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
693
|
-
while (!frame) {
|
|
694
|
-
const cancelToken = { cancelled: false };
|
|
695
|
-
const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
696
|
-
const dataView = deadline
|
|
697
|
-
? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
|
|
698
|
-
cancelToken.cancelled = true;
|
|
699
|
-
})
|
|
700
|
-
: yield transferIn;
|
|
701
|
-
const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
702
|
-
frame = assembler.push(bytes);
|
|
703
|
-
}
|
|
704
|
-
return frame;
|
|
661
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
705
662
|
});
|
|
706
663
|
}
|
|
707
664
|
receiveData(path, timeoutMs) {
|
|
@@ -742,21 +699,50 @@ class WebUsbTransport {
|
|
|
742
699
|
});
|
|
743
700
|
}
|
|
744
701
|
release(path) {
|
|
745
|
-
var _a, _b;
|
|
746
702
|
return __awaiter(this, void 0, void 0, function* () {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
750
|
-
yield device.releaseInterface(ifaceNum);
|
|
751
|
-
yield device.close();
|
|
703
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
704
|
+
yield this.closeOpenDevice(path);
|
|
752
705
|
this.deviceProtocol.delete(path);
|
|
753
706
|
this.deviceProtocolHints.delete(path);
|
|
754
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
755
|
-
this.protocolV2Assemblers.delete(path);
|
|
756
|
-
this.protocolV2Sessions.delete(path);
|
|
757
707
|
this.deviceEndpoints.delete(path);
|
|
758
708
|
});
|
|
759
709
|
}
|
|
710
|
+
getProtocolV2UsbSchemas() {
|
|
711
|
+
if (!this.messages || !this.messagesV2) {
|
|
712
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
protocolV1: this.messages,
|
|
716
|
+
protocolV2: this.messagesV2,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
getProtocolV2UsbLogger() {
|
|
720
|
+
return this.Log;
|
|
721
|
+
}
|
|
722
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
724
|
+
yield this.transferOutOnce(path, frame);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
728
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
729
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
730
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
734
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
735
|
+
yield this.closeOpenDevice(path);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
739
|
+
var _a;
|
|
740
|
+
this.deviceProtocol.delete(path);
|
|
741
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
742
|
+
}
|
|
743
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
744
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
745
|
+
}
|
|
760
746
|
getProtocolType(path) {
|
|
761
747
|
return this.deviceProtocol.get(path);
|
|
762
748
|
}
|
package/dist/webusb.d.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/// <reference types="w3c-web-usb" />
|
|
2
|
-
import transport from '@onekeyfe/hd-transport';
|
|
3
|
-
import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, TransportCallOptions } from '@onekeyfe/hd-transport';
|
|
2
|
+
import transport, { ProtocolV2UsbTransportBase } from '@onekeyfe/hd-transport';
|
|
3
|
+
import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, ProtocolV2CallContext, ProtocolV2Schemas, TransportCallOptions } from '@onekeyfe/hd-transport';
|
|
4
4
|
export interface DeviceInfo extends OneKeyDeviceInfoBase {
|
|
5
5
|
path: string;
|
|
6
6
|
device: USBDevice;
|
|
7
7
|
protocolType?: ProtocolType;
|
|
8
8
|
}
|
|
9
|
-
export default class WebUsbTransport {
|
|
9
|
+
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
10
10
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
11
11
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
12
|
+
private protocolV2SchemaSource;
|
|
12
13
|
private deviceProtocol;
|
|
13
14
|
private deviceProtocolHints;
|
|
14
|
-
private protocolV2Assemblers;
|
|
15
|
-
private protocolV2Sessions;
|
|
16
|
-
private protocolV2Sequences;
|
|
17
15
|
private deviceEndpoints;
|
|
18
16
|
private mockSerialPaths;
|
|
19
17
|
private mockSerialCounter;
|
|
@@ -26,6 +24,7 @@ export default class WebUsbTransport {
|
|
|
26
24
|
configurationId: number;
|
|
27
25
|
endpointId: number;
|
|
28
26
|
interfaceId: number;
|
|
27
|
+
constructor();
|
|
29
28
|
init(logger: any): void;
|
|
30
29
|
configure(signedData: any): void;
|
|
31
30
|
configureProtocolV2(signedData: any): void;
|
|
@@ -53,6 +52,7 @@ export default class WebUsbTransport {
|
|
|
53
52
|
private transferOutWithRetry;
|
|
54
53
|
private transferOutOnce;
|
|
55
54
|
private transferInWithRetry;
|
|
55
|
+
private transferInOnce;
|
|
56
56
|
private resetConnectionAfterProbe;
|
|
57
57
|
private withProtocolReadTimeout;
|
|
58
58
|
private probeProtocolV1;
|
|
@@ -60,9 +60,15 @@ export default class WebUsbTransport {
|
|
|
60
60
|
call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
|
|
61
61
|
private callProtocolV1;
|
|
62
62
|
private callProtocolV2;
|
|
63
|
-
private receiveProtocolV2Frame;
|
|
64
63
|
receiveData(path: string, timeoutMs?: number): Promise<string>;
|
|
65
64
|
release(path: string): Promise<void>;
|
|
65
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
|
|
66
|
+
protected getProtocolV2UsbLogger(): any;
|
|
67
|
+
protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
|
|
68
|
+
protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
|
|
69
|
+
protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
|
|
70
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
|
|
71
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
|
|
66
72
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
67
73
|
}
|
|
68
74
|
//# sourceMappingURL=webusb.d.ts.map
|
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,
|
|
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.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.26",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.26",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.26"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.
|
|
27
|
+
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.26",
|
|
28
28
|
"@types/w3c-web-usb": "^1.0.6",
|
|
29
29
|
"@types/web-bluetooth": "^0.0.17"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "1401fef3eb93f5a83ed5e8375b99181b7aaa6054"
|
|
32
32
|
}
|
package/src/webusb.ts
CHANGED
|
@@ -6,11 +6,8 @@ import transport, {
|
|
|
6
6
|
PROTOCOL_V1_USB_PACKET_SIZE,
|
|
7
7
|
PROTOCOL_V2_CHANNEL_USB,
|
|
8
8
|
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
9
|
-
ProtocolV2FrameAssembler,
|
|
10
9
|
ProtocolV2LinkError,
|
|
11
|
-
|
|
12
|
-
ProtocolV2Session,
|
|
13
|
-
isProtocolV2LinkError,
|
|
10
|
+
ProtocolV2UsbTransportBase,
|
|
14
11
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
15
12
|
} from '@onekeyfe/hd-transport';
|
|
16
13
|
import {
|
|
@@ -28,6 +25,8 @@ import type {
|
|
|
28
25
|
AcquireInput,
|
|
29
26
|
OneKeyDeviceInfoBase,
|
|
30
27
|
ProtocolType,
|
|
28
|
+
ProtocolV2CallContext,
|
|
29
|
+
ProtocolV2Schemas,
|
|
31
30
|
TransportCallOptions,
|
|
32
31
|
} from '@onekeyfe/hd-transport';
|
|
33
32
|
|
|
@@ -68,26 +67,19 @@ interface TransferCancelToken {
|
|
|
68
67
|
cancelled: boolean;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
export default class WebUsbTransport {
|
|
70
|
+
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
72
71
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
73
72
|
|
|
74
73
|
/** Protobuf schema for Protocol V2 transports. */
|
|
75
74
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
76
75
|
|
|
76
|
+
private protocolV2SchemaSource: string | undefined;
|
|
77
|
+
|
|
77
78
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
78
79
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
79
80
|
|
|
80
81
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
81
82
|
|
|
82
|
-
/** Per-device Protocol V2 assembler that retains extra frames from one read. */
|
|
83
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
84
|
-
|
|
85
|
-
/** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
|
|
86
|
-
private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
|
|
87
|
-
|
|
88
|
-
/** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
|
|
89
|
-
private protocolV2Sequences: Map<string, ProtocolV2SequenceCursor> = new Map();
|
|
90
|
-
|
|
91
83
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
92
84
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
93
85
|
|
|
@@ -121,6 +113,14 @@ export default class WebUsbTransport {
|
|
|
121
113
|
|
|
122
114
|
interfaceId = INTERFACE_ID;
|
|
123
115
|
|
|
116
|
+
constructor() {
|
|
117
|
+
super({
|
|
118
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
119
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
120
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
124
|
/**
|
|
125
125
|
* Initialize WebUSB transport
|
|
126
126
|
*/
|
|
@@ -150,8 +150,20 @@ export default class WebUsbTransport {
|
|
|
150
150
|
* Cache the Protocol V2 protobuf schema.
|
|
151
151
|
*/
|
|
152
152
|
configureProtocolV2(signedData: any) {
|
|
153
|
+
const schemaSource =
|
|
154
|
+
typeof signedData === 'string'
|
|
155
|
+
? signedData
|
|
156
|
+
: JSON.stringify(signedData) ?? String(signedData);
|
|
157
|
+
if (schemaSource === this.protocolV2SchemaSource) return;
|
|
158
|
+
|
|
159
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
153
160
|
this.messagesV2 = parseConfigure(signedData);
|
|
154
|
-
this.
|
|
161
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
162
|
+
if (!hadProtocolV2Schema) return;
|
|
163
|
+
|
|
164
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
165
|
+
this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
|
|
166
|
+
);
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
/**
|
|
@@ -240,6 +252,7 @@ export default class WebUsbTransport {
|
|
|
240
252
|
async acquire(input: AcquireInput) {
|
|
241
253
|
if (!input.path) return;
|
|
242
254
|
try {
|
|
255
|
+
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
243
256
|
await this.closeOpenDevice(input.path);
|
|
244
257
|
await this.connect(input.path ?? '', true);
|
|
245
258
|
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
@@ -447,16 +460,12 @@ export default class WebUsbTransport {
|
|
|
447
460
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
448
461
|
const endpoints = this.discoverEndpoints(device);
|
|
449
462
|
this.deviceEndpoints.set(path, endpoints);
|
|
450
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
451
|
-
this.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
452
|
-
|
|
453
463
|
await device.claimInterface(endpoints.interfaceNumber);
|
|
454
464
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
455
465
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
456
466
|
}
|
|
457
467
|
|
|
458
468
|
private async closeOpenDevice(path: string) {
|
|
459
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
460
469
|
const current = this.deviceList.find(device => device.path === path)?.device;
|
|
461
470
|
if (!current?.opened) return;
|
|
462
471
|
|
|
@@ -651,9 +660,19 @@ export default class WebUsbTransport {
|
|
|
651
660
|
throw lastError;
|
|
652
661
|
}
|
|
653
662
|
|
|
663
|
+
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
664
|
+
const device = await this.findDevice(path);
|
|
665
|
+
if (!device.opened) {
|
|
666
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
667
|
+
}
|
|
668
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
669
|
+
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
670
|
+
const result = await device.transferIn(endpointIn, length);
|
|
671
|
+
return this.getTransferInData(result);
|
|
672
|
+
}
|
|
673
|
+
|
|
654
674
|
private async resetConnectionAfterProbe(path: string) {
|
|
655
|
-
this.
|
|
656
|
-
this.protocolV2Sessions.delete(path);
|
|
675
|
+
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
657
676
|
|
|
658
677
|
try {
|
|
659
678
|
const device = await this.findDevice(path);
|
|
@@ -736,7 +755,7 @@ export default class WebUsbTransport {
|
|
|
736
755
|
}
|
|
737
756
|
|
|
738
757
|
return probeProtocolV2Helper({
|
|
739
|
-
call: (name, data, options) => this.callProtocolV2(path, name, data, options
|
|
758
|
+
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
740
759
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
741
760
|
logger: this.Log,
|
|
742
761
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
@@ -818,93 +837,9 @@ export default class WebUsbTransport {
|
|
|
818
837
|
path: string,
|
|
819
838
|
name: string,
|
|
820
839
|
data: Record<string, unknown>,
|
|
821
|
-
options?: TransportCallOptions
|
|
822
|
-
resetOnError = true
|
|
840
|
+
options?: TransportCallOptions
|
|
823
841
|
) {
|
|
824
|
-
|
|
825
|
-
if (!this.messagesV2) {
|
|
826
|
-
throw ERRORS.TypedError(
|
|
827
|
-
HardwareErrorCode.TransportNotConfigured,
|
|
828
|
-
'Protocol V2 schema not configured'
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
if (!protocolV1Messages) {
|
|
832
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
let session = this.protocolV2Sessions.get(path);
|
|
836
|
-
if (!session) {
|
|
837
|
-
let sequenceCursor = this.protocolV2Sequences.get(path);
|
|
838
|
-
if (!sequenceCursor) {
|
|
839
|
-
sequenceCursor = new ProtocolV2SequenceCursor();
|
|
840
|
-
this.protocolV2Sequences.set(path, sequenceCursor);
|
|
841
|
-
}
|
|
842
|
-
session = new ProtocolV2Session({
|
|
843
|
-
schemas: {
|
|
844
|
-
protocolV1: protocolV1Messages,
|
|
845
|
-
protocolV2: this.messagesV2,
|
|
846
|
-
},
|
|
847
|
-
router: PROTOCOL_V2_CHANNEL_USB,
|
|
848
|
-
sequenceCursor,
|
|
849
|
-
writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
|
|
850
|
-
readFrame: context => this.receiveProtocolV2Frame(path, context.timeoutMs),
|
|
851
|
-
logger: this.Log,
|
|
852
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
853
|
-
createTimeoutError: (messageName: string, timeoutMs: number) =>
|
|
854
|
-
new ProtocolV2LinkError(
|
|
855
|
-
'response-timeout',
|
|
856
|
-
`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`
|
|
857
|
-
),
|
|
858
|
-
});
|
|
859
|
-
this.protocolV2Sessions.set(path, session);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
try {
|
|
863
|
-
return await session.call(name, data, options);
|
|
864
|
-
} catch (error) {
|
|
865
|
-
if (resetOnError && (isProtocolV2LinkError(error) || this.isRetryablePacketIoError(error))) {
|
|
866
|
-
try {
|
|
867
|
-
await this.resetConnectionAfterProbe(path);
|
|
868
|
-
} catch (resetError) {
|
|
869
|
-
this.Log.debug('[WebUsbTransport] Protocol V2 link reset failed:', resetError);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
throw error;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
|
|
877
|
-
let assembler = this.protocolV2Assemblers.get(path);
|
|
878
|
-
if (!assembler) {
|
|
879
|
-
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
880
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
|
|
884
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
885
|
-
|
|
886
|
-
while (!frame) {
|
|
887
|
-
const cancelToken = { cancelled: false };
|
|
888
|
-
const transferIn = this.transferInWithRetry(path, PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
889
|
-
const dataView = deadline
|
|
890
|
-
? await this.withProtocolReadTimeout(
|
|
891
|
-
path,
|
|
892
|
-
transferIn,
|
|
893
|
-
Math.max(deadline - Date.now(), 1),
|
|
894
|
-
'V2',
|
|
895
|
-
() => {
|
|
896
|
-
cancelToken.cancelled = true;
|
|
897
|
-
}
|
|
898
|
-
)
|
|
899
|
-
: await transferIn;
|
|
900
|
-
const bytes = new Uint8Array(
|
|
901
|
-
this.toArrayBuffer(
|
|
902
|
-
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
903
|
-
)
|
|
904
|
-
);
|
|
905
|
-
frame = assembler.push(bytes);
|
|
906
|
-
}
|
|
907
|
-
return frame;
|
|
842
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
908
843
|
}
|
|
909
844
|
|
|
910
845
|
/**
|
|
@@ -959,19 +894,63 @@ export default class WebUsbTransport {
|
|
|
959
894
|
* Release device
|
|
960
895
|
*/
|
|
961
896
|
async release(path: string) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
965
|
-
await device.releaseInterface(ifaceNum);
|
|
966
|
-
await device.close();
|
|
897
|
+
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
898
|
+
await this.closeOpenDevice(path);
|
|
967
899
|
this.deviceProtocol.delete(path);
|
|
968
900
|
this.deviceProtocolHints.delete(path);
|
|
969
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
970
|
-
this.protocolV2Assemblers.delete(path);
|
|
971
|
-
this.protocolV2Sessions.delete(path);
|
|
972
901
|
this.deviceEndpoints.delete(path);
|
|
973
902
|
}
|
|
974
903
|
|
|
904
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
905
|
+
if (!this.messages || !this.messagesV2) {
|
|
906
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
protocolV1: this.messages,
|
|
910
|
+
protocolV2: this.messagesV2,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
protected getProtocolV2UsbLogger() {
|
|
915
|
+
return this.Log;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
protected async writeProtocolV2UsbPacket(
|
|
919
|
+
path: string,
|
|
920
|
+
frame: Uint8Array,
|
|
921
|
+
_context: ProtocolV2CallContext
|
|
922
|
+
): Promise<void> {
|
|
923
|
+
await this.transferOutOnce(path, frame);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
protected async readProtocolV2UsbPacket(
|
|
927
|
+
path: string,
|
|
928
|
+
_context: ProtocolV2CallContext
|
|
929
|
+
): Promise<Uint8Array> {
|
|
930
|
+
const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
931
|
+
return new Uint8Array(
|
|
932
|
+
this.toArrayBuffer(
|
|
933
|
+
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
934
|
+
)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
939
|
+
await this.closeOpenDevice(path);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
943
|
+
this.deviceProtocol.delete(path);
|
|
944
|
+
this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
948
|
+
return new ProtocolV2LinkError(
|
|
949
|
+
'response-timeout',
|
|
950
|
+
`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
975
954
|
/**
|
|
976
955
|
* Expose the detected protocol type for a given device path.
|
|
977
956
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|