@onekeyfe/hd-transport-usb 1.2.0-alpha.7 → 1.2.0-alpha.9
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__/protocol-v2-link.test.ts +260 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -96
- package/package.json +4 -4
- package/src/index.ts +89 -127
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import transportPackage, { PROTOCOL_V2_CHANNEL_USB, ProtocolV2 } from '@onekeyfe/hd-transport';
|
|
2
|
+
|
|
3
|
+
import NodeUsbTransport from '../src';
|
|
4
|
+
|
|
5
|
+
let mockUsbDevices: any[] = [];
|
|
6
|
+
|
|
7
|
+
jest.mock('usb', () => ({
|
|
8
|
+
getDeviceList: jest.fn(() => mockUsbDevices),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const { parseConfigure } = transportPackage;
|
|
12
|
+
|
|
13
|
+
const protocolV1Schema = {
|
|
14
|
+
nested: {
|
|
15
|
+
Initialize: { fields: {} },
|
|
16
|
+
Success: {
|
|
17
|
+
fields: {
|
|
18
|
+
message: { type: 'string', id: 1 },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
MessageType: {
|
|
22
|
+
values: {
|
|
23
|
+
MessageType_Initialize: 1,
|
|
24
|
+
MessageType_Success: 2,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const protocolV2Schema = {
|
|
31
|
+
nested: {
|
|
32
|
+
Ping: {
|
|
33
|
+
fields: {
|
|
34
|
+
message: { type: 'string', id: 1 },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
Success: {
|
|
38
|
+
fields: {
|
|
39
|
+
message: { type: 'string', id: 1 },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
MessageType: {
|
|
43
|
+
values: {
|
|
44
|
+
MessageType_Ping: 60206,
|
|
45
|
+
MessageType_Success: 60207,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const schemas = {
|
|
52
|
+
protocolV1: parseConfigure(protocolV1Schema),
|
|
53
|
+
protocolV2: parseConfigure(protocolV2Schema),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type PendingRead = {
|
|
57
|
+
started: Promise<void>;
|
|
58
|
+
fail: (error?: Error) => void;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const createHarness = () => {
|
|
62
|
+
const path = '6136';
|
|
63
|
+
const responseQueue: Buffer[] = [];
|
|
64
|
+
const sentSeqs: number[] = [];
|
|
65
|
+
let writeError: Error | undefined;
|
|
66
|
+
let holdNextRead:
|
|
67
|
+
| {
|
|
68
|
+
markStarted: () => void;
|
|
69
|
+
started: Promise<void>;
|
|
70
|
+
callback?: (error?: Error, data?: Buffer) => void;
|
|
71
|
+
}
|
|
72
|
+
| undefined;
|
|
73
|
+
|
|
74
|
+
const epIn = {
|
|
75
|
+
direction: 'in',
|
|
76
|
+
address: 0x81,
|
|
77
|
+
timeout: 30_000,
|
|
78
|
+
transfer: jest.fn((_length: number, callback: (error?: Error, data?: Buffer) => void) => {
|
|
79
|
+
if (epIn.timeout === 50) {
|
|
80
|
+
callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (holdNextRead) {
|
|
84
|
+
const pending = holdNextRead;
|
|
85
|
+
holdNextRead = undefined;
|
|
86
|
+
pending.callback = callback;
|
|
87
|
+
pending.markStarted();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const response = responseQueue.shift();
|
|
91
|
+
if (!response) {
|
|
92
|
+
callback(new Error('LIBUSB_TRANSFER_TIMED_OUT'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
callback(undefined, response);
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const epOut = {
|
|
100
|
+
direction: 'out',
|
|
101
|
+
address: 0x01,
|
|
102
|
+
timeout: 30_000,
|
|
103
|
+
transfer: jest.fn((data: Buffer, callback: (error?: Error) => void) => {
|
|
104
|
+
const seq = data[6];
|
|
105
|
+
sentSeqs.push(seq);
|
|
106
|
+
if (writeError) {
|
|
107
|
+
const error = writeError;
|
|
108
|
+
writeError = undefined;
|
|
109
|
+
callback(error);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
responseQueue.push(
|
|
113
|
+
Buffer.from(
|
|
114
|
+
ProtocolV2.encodeFrame(
|
|
115
|
+
schemas,
|
|
116
|
+
'Success',
|
|
117
|
+
{ message: 'ok' },
|
|
118
|
+
{ router: PROTOCOL_V2_CHANNEL_USB, seq }
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
callback();
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const iface = {
|
|
127
|
+
descriptor: { bInterfaceClass: 0xff, bInterfaceNumber: 0 },
|
|
128
|
+
endpoints: [epIn, epOut],
|
|
129
|
+
claim: jest.fn(),
|
|
130
|
+
release: jest.fn((callback: () => void) => callback()),
|
|
131
|
+
isKernelDriverActive: jest.fn(() => false),
|
|
132
|
+
detachKernelDriver: jest.fn(),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const device = {
|
|
136
|
+
busNumber: 1,
|
|
137
|
+
deviceAddress: 2,
|
|
138
|
+
timeout: 30_000,
|
|
139
|
+
deviceDescriptor: {
|
|
140
|
+
idVendor: 0x1209,
|
|
141
|
+
idProduct: 0x4f4a,
|
|
142
|
+
iSerialNumber: 1,
|
|
143
|
+
},
|
|
144
|
+
interfaces: [iface],
|
|
145
|
+
open: jest.fn(),
|
|
146
|
+
close: jest.fn(),
|
|
147
|
+
getStringDescriptor: jest.fn(
|
|
148
|
+
(_index: number, callback: (error?: Error, value?: string) => void) =>
|
|
149
|
+
callback(undefined, path)
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
mockUsbDevices = [device];
|
|
154
|
+
const transport = new NodeUsbTransport();
|
|
155
|
+
transport.init({ debug: jest.fn(), error: jest.fn() });
|
|
156
|
+
transport.configure(protocolV1Schema);
|
|
157
|
+
transport.configureProtocolV2(protocolV2Schema);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
transport,
|
|
161
|
+
path,
|
|
162
|
+
device,
|
|
163
|
+
iface,
|
|
164
|
+
epIn,
|
|
165
|
+
epOut,
|
|
166
|
+
sentSeqs,
|
|
167
|
+
async acquire() {
|
|
168
|
+
await transport.enumerate();
|
|
169
|
+
await transport.acquire({ path, expectedProtocol: 'V2' });
|
|
170
|
+
},
|
|
171
|
+
failNextWrite(error: Error) {
|
|
172
|
+
writeError = error;
|
|
173
|
+
},
|
|
174
|
+
holdRead(): PendingRead {
|
|
175
|
+
let markStarted: () => void = () => undefined;
|
|
176
|
+
const started = new Promise<void>(resolve => {
|
|
177
|
+
markStarted = resolve;
|
|
178
|
+
});
|
|
179
|
+
const pending = { markStarted, started, callback: undefined };
|
|
180
|
+
holdNextRead = pending;
|
|
181
|
+
return {
|
|
182
|
+
started,
|
|
183
|
+
fail(error = new Error('read released after test')) {
|
|
184
|
+
pending.callback?.(error);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
describe('NodeUsbTransport Protocol V2 link lifecycle', () => {
|
|
192
|
+
test('keeps seq across probe, call and reacquire', async () => {
|
|
193
|
+
const harness = createHarness();
|
|
194
|
+
const { transport, path, sentSeqs } = harness;
|
|
195
|
+
|
|
196
|
+
await harness.acquire();
|
|
197
|
+
await transport.call(path, 'Ping', { message: 'first' });
|
|
198
|
+
await transport.release(path);
|
|
199
|
+
await harness.acquire();
|
|
200
|
+
await transport.call(path, 'Ping', { message: 'second' });
|
|
201
|
+
|
|
202
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
203
|
+
await transport.release(path);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('does not resend a Protocol V2 frame after transferOut fails', async () => {
|
|
207
|
+
const harness = createHarness();
|
|
208
|
+
const { transport, path, epOut } = harness;
|
|
209
|
+
await harness.acquire();
|
|
210
|
+
epOut.transfer.mockClear();
|
|
211
|
+
harness.failNextWrite(new Error('LIBUSB_ERROR_IO'));
|
|
212
|
+
|
|
213
|
+
await expect(transport.call(path, 'Ping', { message: 'write-failure' })).rejects.toThrow(
|
|
214
|
+
'LIBUSB_ERROR_IO'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(epOut.transfer).toHaveBeenCalledTimes(1);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('rejects a pending read when release invalidates the link', async () => {
|
|
221
|
+
const harness = createHarness();
|
|
222
|
+
const { transport, path } = harness;
|
|
223
|
+
await harness.acquire();
|
|
224
|
+
const pendingRead = harness.holdRead();
|
|
225
|
+
|
|
226
|
+
const call = transport.call(path, 'Ping', { message: 'pending' }, { timeoutMs: 5000 });
|
|
227
|
+
const outcome = call.then(
|
|
228
|
+
() => 'resolved',
|
|
229
|
+
error => error.message
|
|
230
|
+
);
|
|
231
|
+
await pendingRead.started;
|
|
232
|
+
await transport.release(path);
|
|
233
|
+
const settled = await Promise.race([
|
|
234
|
+
outcome,
|
|
235
|
+
new Promise<string>(resolve => {
|
|
236
|
+
setTimeout(() => resolve('still pending'), 50);
|
|
237
|
+
}),
|
|
238
|
+
]);
|
|
239
|
+
pendingRead.fail();
|
|
240
|
+
|
|
241
|
+
expect(settled).not.toBe('still pending');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('keeps the cursor after a response timeout rebuilds the USB connection', async () => {
|
|
245
|
+
const harness = createHarness();
|
|
246
|
+
const { transport, path, sentSeqs } = harness;
|
|
247
|
+
await harness.acquire();
|
|
248
|
+
const pendingRead = harness.holdRead();
|
|
249
|
+
|
|
250
|
+
await expect(
|
|
251
|
+
transport.call(path, 'Ping', { message: 'timeout' }, { timeoutMs: 20 })
|
|
252
|
+
).rejects.toThrow('20ms');
|
|
253
|
+
pendingRead.fail();
|
|
254
|
+
await harness.acquire();
|
|
255
|
+
await transport.call(path, 'Ping', { message: 'after-timeout' });
|
|
256
|
+
|
|
257
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
258
|
+
await transport.release(path);
|
|
259
|
+
});
|
|
260
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as transport from '@onekeyfe/hd-transport';
|
|
2
|
-
import transport__default, { OneKeyDeviceInfo, AcquireInput, TransportCallOptions, ProtocolType } from '@onekeyfe/hd-transport';
|
|
2
|
+
import transport__default, { ProtocolV2UsbTransportBase, OneKeyDeviceInfo, AcquireInput, TransportCallOptions, ProtocolV2Schemas, ProtocolV2CallContext, ProtocolType } from '@onekeyfe/hd-transport';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
|
|
5
|
-
declare class NodeUsbTransport {
|
|
5
|
+
declare class NodeUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
6
6
|
messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
7
7
|
messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
|
|
8
8
|
name: string;
|
|
@@ -14,11 +14,9 @@ declare class NodeUsbTransport {
|
|
|
14
14
|
private serialToBusId;
|
|
15
15
|
private openDevices;
|
|
16
16
|
private deviceProtocol;
|
|
17
|
-
private protocolV2Assemblers;
|
|
18
|
-
private protocolV2Sessions;
|
|
19
|
-
private protocolV2ReadTimeouts;
|
|
20
17
|
private reconnectLocks;
|
|
21
18
|
private cancelled;
|
|
19
|
+
constructor();
|
|
22
20
|
init(logger: any, emitter?: EventEmitter): Promise<string>;
|
|
23
21
|
configure(signedData: any): Promise<void>;
|
|
24
22
|
configureProtocolV2(signedData: any): void;
|
|
@@ -55,8 +53,13 @@ declare class NodeUsbTransport {
|
|
|
55
53
|
private withProtocolReadTimeout;
|
|
56
54
|
private probeProtocolV1;
|
|
57
55
|
private probeProtocolV2;
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
|
|
57
|
+
protected getProtocolV2UsbLogger(): any;
|
|
58
|
+
protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
|
|
59
|
+
protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
|
|
60
|
+
protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
|
|
61
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
|
|
62
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
|
|
60
63
|
private callProtocolV2;
|
|
61
64
|
private receiveData;
|
|
62
65
|
getProtocolType(path: string): ProtocolType | undefined;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -161,8 +161,13 @@ function skipReportByte(packet) {
|
|
|
161
161
|
function toArrayBuffer(buf) {
|
|
162
162
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
163
163
|
}
|
|
164
|
-
class NodeUsbTransport {
|
|
164
|
+
class NodeUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
165
165
|
constructor() {
|
|
166
|
+
super({
|
|
167
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
168
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
169
|
+
logPrefix: 'ProtocolV2 NodeUSB',
|
|
170
|
+
});
|
|
166
171
|
this.name = 'NodeUsbTransport';
|
|
167
172
|
this.version = '';
|
|
168
173
|
this.configured = false;
|
|
@@ -170,9 +175,6 @@ class NodeUsbTransport {
|
|
|
170
175
|
this.serialToBusId = new Map();
|
|
171
176
|
this.openDevices = new Map();
|
|
172
177
|
this.deviceProtocol = new Map();
|
|
173
|
-
this.protocolV2Assemblers = new Map();
|
|
174
|
-
this.protocolV2Sessions = new Map();
|
|
175
|
-
this.protocolV2ReadTimeouts = new Map();
|
|
176
178
|
this.reconnectLocks = new Map();
|
|
177
179
|
this.cancelled = false;
|
|
178
180
|
}
|
|
@@ -190,13 +192,13 @@ class NodeUsbTransport {
|
|
|
190
192
|
configureProtocolV2(signedData) {
|
|
191
193
|
var _a;
|
|
192
194
|
this.messagesV2 = parseConfigure(signedData);
|
|
193
|
-
this.
|
|
194
|
-
this.protocolV2ReadTimeouts.clear();
|
|
195
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] schema link cleanup failed:', error); });
|
|
195
196
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] Protocol V2 schema configured');
|
|
196
197
|
}
|
|
197
198
|
listen() {
|
|
198
199
|
}
|
|
199
200
|
stop() {
|
|
201
|
+
this.disposeProtocolV2UsbLinks('Node USB transport stopped').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] stop link cleanup failed:', error); });
|
|
200
202
|
}
|
|
201
203
|
post(path, name, data) {
|
|
202
204
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -254,6 +256,7 @@ class NodeUsbTransport {
|
|
|
254
256
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, 'No device path provided');
|
|
255
257
|
}
|
|
256
258
|
try {
|
|
259
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'Node USB transport acquired');
|
|
257
260
|
yield this.closeOpenDevice(path);
|
|
258
261
|
yield this.openDevice(path);
|
|
259
262
|
yield this.detectProtocol(path, input.expectedProtocol);
|
|
@@ -267,9 +270,9 @@ class NodeUsbTransport {
|
|
|
267
270
|
}
|
|
268
271
|
release(path, _onclose) {
|
|
269
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
yield this.invalidateProtocolV2UsbLink(path, 'Node USB transport released');
|
|
270
274
|
yield this.closeOpenDevice(path);
|
|
271
275
|
this.deviceProtocol.delete(path);
|
|
272
|
-
this.protocolV2Assemblers.delete(path);
|
|
273
276
|
});
|
|
274
277
|
}
|
|
275
278
|
closeOpenDevice(path) {
|
|
@@ -400,6 +403,7 @@ class NodeUsbTransport {
|
|
|
400
403
|
var _a, _b;
|
|
401
404
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
402
405
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
406
|
+
yield this.rotateProtocolV2UsbGeneration(path, `Node USB Protocol V1 ${direction} reconnect attempt ${attempt}`);
|
|
403
407
|
try {
|
|
404
408
|
yield this.closeOpenDevice(path);
|
|
405
409
|
}
|
|
@@ -472,19 +476,20 @@ class NodeUsbTransport {
|
|
|
472
476
|
lastError = error;
|
|
473
477
|
if ((options === null || options === void 0 ? void 0 : options.waitIndefinitelyOnTimeout) && this.isUsbTransferTimeout(error)) {
|
|
474
478
|
attempt -= 1;
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
|
|
478
|
-
if (!shouldRetry) {
|
|
479
|
-
throw error;
|
|
480
|
-
}
|
|
481
|
-
try {
|
|
482
|
-
currentDev = yield this.reconnectForRetry(path, 'in', attempt, error);
|
|
483
479
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
(
|
|
487
|
-
|
|
480
|
+
else {
|
|
481
|
+
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
|
|
482
|
+
if (!shouldRetry) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
currentDev = yield this.reconnectForRetry(path, 'in', attempt, error);
|
|
487
|
+
}
|
|
488
|
+
catch (reconnectError) {
|
|
489
|
+
lastError = reconnectError;
|
|
490
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
488
493
|
}
|
|
489
494
|
}
|
|
490
495
|
}
|
|
@@ -601,16 +606,14 @@ class NodeUsbTransport {
|
|
|
601
606
|
});
|
|
602
607
|
}
|
|
603
608
|
resetConnectionAfterProbe(path) {
|
|
604
|
-
var _a
|
|
609
|
+
var _a;
|
|
605
610
|
return __awaiter(this, void 0, void 0, function* () {
|
|
606
|
-
|
|
607
|
-
this.protocolV2Sessions.delete(path);
|
|
608
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
611
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'Node USB protocol probe reset');
|
|
609
612
|
try {
|
|
610
613
|
yield this.closeOpenDevice(path);
|
|
611
614
|
}
|
|
612
615
|
catch (error) {
|
|
613
|
-
(
|
|
616
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[NodeUsbTransport] close after protocol probe error:', error);
|
|
614
617
|
}
|
|
615
618
|
yield this.enumerate();
|
|
616
619
|
yield this.openDevice(path);
|
|
@@ -683,96 +686,60 @@ class NodeUsbTransport {
|
|
|
683
686
|
});
|
|
684
687
|
});
|
|
685
688
|
}
|
|
686
|
-
|
|
687
|
-
|
|
689
|
+
getProtocolV2UsbSchemas() {
|
|
690
|
+
if (!this.messages || !this.messagesV2) {
|
|
691
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
protocolV1: this.messages,
|
|
695
|
+
protocolV2: this.messagesV2,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
getProtocolV2UsbLogger() {
|
|
699
|
+
return this.Log;
|
|
700
|
+
}
|
|
701
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
688
702
|
return __awaiter(this, void 0, void 0, function* () {
|
|
689
|
-
|
|
690
|
-
|
|
703
|
+
if (this.cancelled) {
|
|
704
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
|
|
705
|
+
}
|
|
706
|
+
yield transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
710
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
711
|
+
for (;;) {
|
|
691
712
|
if (this.cancelled) {
|
|
692
713
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
|
|
693
714
|
}
|
|
694
715
|
try {
|
|
695
|
-
yield
|
|
696
|
-
return;
|
|
716
|
+
const packet = yield transferInOnce(this.getOpenDevice(path).epIn, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
717
|
+
return new Uint8Array(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
697
718
|
}
|
|
698
719
|
catch (error) {
|
|
699
|
-
|
|
700
|
-
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
|
|
701
|
-
if (!shouldRetry) {
|
|
720
|
+
if (!this.isUsbTransferTimeout(error)) {
|
|
702
721
|
throw error;
|
|
703
722
|
}
|
|
704
|
-
try {
|
|
705
|
-
yield this.reconnectForRetry(path, 'out', attempt, error);
|
|
706
|
-
}
|
|
707
|
-
catch (reconnectError) {
|
|
708
|
-
lastError = reconnectError;
|
|
709
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] Protocol V2 write reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
710
|
-
break;
|
|
711
|
-
}
|
|
712
723
|
}
|
|
713
724
|
}
|
|
714
|
-
throw lastError;
|
|
715
725
|
});
|
|
716
726
|
}
|
|
717
|
-
|
|
727
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
718
728
|
return __awaiter(this, void 0, void 0, function* () {
|
|
719
|
-
|
|
720
|
-
if (!assembler) {
|
|
721
|
-
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
722
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
723
|
-
}
|
|
724
|
-
let frame = assembler.push(new Uint8Array(0));
|
|
725
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
726
|
-
while (!frame) {
|
|
727
|
-
const transferIn = this.transferInWithRetry(path, this.getOpenDevice(path), transport.PROTOCOL_V2_FRAME_MAX_BYTES, { waitIndefinitelyOnTimeout: !deadline });
|
|
728
|
-
const packet = deadline
|
|
729
|
-
? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2')
|
|
730
|
-
: yield transferIn;
|
|
731
|
-
const bytes = new Uint8Array(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
732
|
-
try {
|
|
733
|
-
frame = assembler.push(bytes);
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
return frame;
|
|
729
|
+
yield this.closeOpenDevice(path);
|
|
740
730
|
});
|
|
741
731
|
}
|
|
742
|
-
|
|
732
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
743
733
|
var _a;
|
|
734
|
+
this.deviceProtocol.delete(path);
|
|
735
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[NodeUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
736
|
+
}
|
|
737
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
738
|
+
return new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
739
|
+
}
|
|
740
|
+
callProtocolV2(path, name, data, options) {
|
|
744
741
|
return __awaiter(this, void 0, void 0, function* () {
|
|
745
|
-
|
|
746
|
-
if (!this.messagesV2) {
|
|
747
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
748
|
-
}
|
|
749
|
-
if (!protocolV1Messages) {
|
|
750
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
751
|
-
}
|
|
752
|
-
let session = this.protocolV2Sessions.get(path);
|
|
753
|
-
if (!session) {
|
|
754
|
-
session = new transport.ProtocolV2Session({
|
|
755
|
-
schemas: {
|
|
756
|
-
protocolV1: protocolV1Messages,
|
|
757
|
-
protocolV2: this.messagesV2,
|
|
758
|
-
},
|
|
759
|
-
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
760
|
-
writeFrame: (frame) => this.writeProtocolV2Frame(path, frame),
|
|
761
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
762
|
-
logger: this.Log,
|
|
763
|
-
logPrefix: 'ProtocolV2 NodeUSB',
|
|
764
|
-
createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
765
|
-
});
|
|
766
|
-
this.protocolV2Sessions.set(path, session);
|
|
767
|
-
}
|
|
768
|
-
this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
769
|
-
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
770
|
-
try {
|
|
771
|
-
return yield session.call(name, data, options);
|
|
772
|
-
}
|
|
773
|
-
finally {
|
|
774
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
775
|
-
}
|
|
742
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
776
743
|
});
|
|
777
744
|
}
|
|
778
745
|
receiveData(path, dev, timeoutMs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-usb",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.9",
|
|
4
4
|
"description": "OneKey hardware wallet direct USB transport plugin (libusb)",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,10 +20,10 @@
|
|
|
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.9",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.9",
|
|
25
25
|
"bytebuffer": "^5.0.1",
|
|
26
26
|
"usb": "^2.14.0"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "f90ef9921b742f68cce9f4e7a0425e45e8ae7ec9"
|
|
29
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,8 +8,7 @@ import transport, {
|
|
|
8
8
|
PROTOCOL_V1_USB_PACKET_SIZE,
|
|
9
9
|
PROTOCOL_V2_CHANNEL_USB,
|
|
10
10
|
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
11
|
-
|
|
12
|
-
ProtocolV2Session,
|
|
11
|
+
ProtocolV2UsbTransportBase,
|
|
13
12
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
14
13
|
} from '@onekeyfe/hd-transport';
|
|
15
14
|
import { ERRORS, HardwareErrorCode, ONEKEY_WEBUSB_FILTER, wait } from '@onekeyfe/hd-shared';
|
|
@@ -19,6 +18,8 @@ import type {
|
|
|
19
18
|
AcquireInput,
|
|
20
19
|
OneKeyDeviceInfo,
|
|
21
20
|
ProtocolType,
|
|
21
|
+
ProtocolV2CallContext,
|
|
22
|
+
ProtocolV2Schemas,
|
|
22
23
|
TransportCallOptions,
|
|
23
24
|
} from '@onekeyfe/hd-transport';
|
|
24
25
|
|
|
@@ -180,7 +181,7 @@ function toArrayBuffer(buf: Buffer): ArrayBuffer {
|
|
|
180
181
|
*
|
|
181
182
|
* Modeled after WebUsbTransport.
|
|
182
183
|
*/
|
|
183
|
-
export default class NodeUsbTransport {
|
|
184
|
+
export default class NodeUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
184
185
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
185
186
|
|
|
186
187
|
/** Protobuf schema for Protocol V2 transports. */
|
|
@@ -207,21 +208,20 @@ export default class NodeUsbTransport {
|
|
|
207
208
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
208
209
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
209
210
|
|
|
210
|
-
/** Per-path Protocol V2 frame assembler, preserving buffered frames during reads. */
|
|
211
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
212
|
-
|
|
213
|
-
/** Per-path Protocol V2 session, preserving seq across API calls on the same device path. */
|
|
214
|
-
private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
|
|
215
|
-
|
|
216
|
-
/** Current Protocol V2 read timeout, consumed by cached session readFrame closures. */
|
|
217
|
-
private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
|
|
218
|
-
|
|
219
211
|
/** per-path reconnect lock to prevent concurrent reconnects */
|
|
220
212
|
private reconnectLocks = new Map<string, Promise<OpenDevice>>();
|
|
221
213
|
|
|
222
214
|
/** set to true when cancel() is called; checked by retry loops */
|
|
223
215
|
private cancelled = false;
|
|
224
216
|
|
|
217
|
+
constructor() {
|
|
218
|
+
super({
|
|
219
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
220
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
221
|
+
logPrefix: 'ProtocolV2 NodeUSB',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
225
|
/**
|
|
226
226
|
* Initialize transport.
|
|
227
227
|
* Signature matches the Transport.init interface (logger, emitter).
|
|
@@ -241,8 +241,9 @@ export default class NodeUsbTransport {
|
|
|
241
241
|
|
|
242
242
|
configureProtocolV2(signedData: any) {
|
|
243
243
|
this.messagesV2 = parseConfigure(signedData);
|
|
244
|
-
this.
|
|
245
|
-
|
|
244
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
245
|
+
this.Log?.debug('[NodeUsbTransport] schema link cleanup failed:', error)
|
|
246
|
+
);
|
|
246
247
|
this.Log?.debug('[NodeUsbTransport] Protocol V2 schema configured');
|
|
247
248
|
}
|
|
248
249
|
|
|
@@ -251,7 +252,9 @@ export default class NodeUsbTransport {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
stop() {
|
|
254
|
-
|
|
255
|
+
this.disposeProtocolV2UsbLinks('Node USB transport stopped').catch(error =>
|
|
256
|
+
this.Log?.debug('[NodeUsbTransport] stop link cleanup failed:', error)
|
|
257
|
+
);
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
/**
|
|
@@ -326,6 +329,7 @@ export default class NodeUsbTransport {
|
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
try {
|
|
332
|
+
await this.rotateProtocolV2UsbGeneration(path, 'Node USB transport acquired');
|
|
329
333
|
await this.closeOpenDevice(path);
|
|
330
334
|
await this.openDevice(path);
|
|
331
335
|
await this.detectProtocol(path, input.expectedProtocol);
|
|
@@ -340,9 +344,9 @@ export default class NodeUsbTransport {
|
|
|
340
344
|
* Release device — release interface and close.
|
|
341
345
|
*/
|
|
342
346
|
async release(path: string, _onclose?: boolean): Promise<void> {
|
|
347
|
+
await this.invalidateProtocolV2UsbLink(path, 'Node USB transport released');
|
|
343
348
|
await this.closeOpenDevice(path);
|
|
344
349
|
this.deviceProtocol.delete(path);
|
|
345
|
-
this.protocolV2Assemblers.delete(path);
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
private async closeOpenDevice(path: string): Promise<void> {
|
|
@@ -531,6 +535,10 @@ export default class NodeUsbTransport {
|
|
|
531
535
|
);
|
|
532
536
|
await wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
533
537
|
|
|
538
|
+
await this.rotateProtocolV2UsbGeneration(
|
|
539
|
+
path,
|
|
540
|
+
`Node USB Protocol V1 ${direction} reconnect attempt ${attempt}`
|
|
541
|
+
);
|
|
534
542
|
// Close the existing device without clearing the detected protocol cache.
|
|
535
543
|
try {
|
|
536
544
|
await this.closeOpenDevice(path);
|
|
@@ -623,22 +631,22 @@ export default class NodeUsbTransport {
|
|
|
623
631
|
lastError = error;
|
|
624
632
|
if (options?.waitIndefinitelyOnTimeout && this.isUsbTransferTimeout(error)) {
|
|
625
633
|
attempt -= 1;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
|
|
634
|
+
} else {
|
|
635
|
+
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
|
|
636
|
+
if (!shouldRetry) {
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
currentDev = await this.reconnectForRetry(path, 'in', attempt, error);
|
|
641
|
+
} catch (reconnectError) {
|
|
642
|
+
lastError = reconnectError;
|
|
643
|
+
this.Log?.debug(
|
|
644
|
+
`[NodeUsbTransport] reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(
|
|
645
|
+
reconnectError
|
|
646
|
+
)}`
|
|
647
|
+
);
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
642
650
|
}
|
|
643
651
|
}
|
|
644
652
|
}
|
|
@@ -787,9 +795,7 @@ export default class NodeUsbTransport {
|
|
|
787
795
|
}
|
|
788
796
|
|
|
789
797
|
private async resetConnectionAfterProbe(path: string) {
|
|
790
|
-
this.
|
|
791
|
-
this.protocolV2Sessions.delete(path);
|
|
792
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
798
|
+
await this.rotateProtocolV2UsbGeneration(path, 'Node USB protocol probe reset');
|
|
793
799
|
|
|
794
800
|
try {
|
|
795
801
|
await this.closeOpenDevice(path);
|
|
@@ -871,75 +877,66 @@ export default class NodeUsbTransport {
|
|
|
871
877
|
});
|
|
872
878
|
}
|
|
873
879
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
880
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
881
|
+
if (!this.messages || !this.messagesV2) {
|
|
882
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
protocolV1: this.messages,
|
|
886
|
+
protocolV2: this.messagesV2,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
protected getProtocolV2UsbLogger() {
|
|
891
|
+
return this.Log;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
protected async writeProtocolV2UsbPacket(
|
|
895
|
+
path: string,
|
|
896
|
+
frame: Uint8Array,
|
|
897
|
+
_context: ProtocolV2CallContext
|
|
898
|
+
): Promise<void> {
|
|
899
|
+
if (this.cancelled) {
|
|
900
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
|
|
901
|
+
}
|
|
902
|
+
await transferOutOnce(this.getOpenDevice(path).epOut, Buffer.from(frame));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
protected async readProtocolV2UsbPacket(
|
|
906
|
+
path: string,
|
|
907
|
+
_context: ProtocolV2CallContext
|
|
908
|
+
): Promise<Uint8Array> {
|
|
909
|
+
for (;;) {
|
|
877
910
|
if (this.cancelled) {
|
|
878
911
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromOutside, 'Cancelled');
|
|
879
912
|
}
|
|
880
913
|
try {
|
|
881
|
-
await
|
|
882
|
-
|
|
914
|
+
const packet = await transferInOnce(
|
|
915
|
+
this.getOpenDevice(path).epIn,
|
|
916
|
+
PROTOCOL_V2_FRAME_MAX_BYTES
|
|
917
|
+
);
|
|
918
|
+
return new Uint8Array(
|
|
919
|
+
packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
|
|
920
|
+
);
|
|
883
921
|
} catch (error) {
|
|
884
|
-
|
|
885
|
-
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryableError(error);
|
|
886
|
-
if (!shouldRetry) {
|
|
922
|
+
if (!this.isUsbTransferTimeout(error)) {
|
|
887
923
|
throw error;
|
|
888
924
|
}
|
|
889
|
-
try {
|
|
890
|
-
await this.reconnectForRetry(path, 'out', attempt, error);
|
|
891
|
-
} catch (reconnectError) {
|
|
892
|
-
lastError = reconnectError;
|
|
893
|
-
this.Log?.debug(
|
|
894
|
-
`[NodeUsbTransport] Protocol V2 write reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(
|
|
895
|
-
reconnectError
|
|
896
|
-
)}`
|
|
897
|
-
);
|
|
898
|
-
break;
|
|
899
|
-
}
|
|
900
925
|
}
|
|
901
926
|
}
|
|
902
|
-
throw lastError;
|
|
903
927
|
}
|
|
904
928
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
909
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
910
|
-
}
|
|
929
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
930
|
+
await this.closeOpenDevice(path);
|
|
931
|
+
}
|
|
911
932
|
|
|
912
|
-
|
|
913
|
-
|
|
933
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
934
|
+
this.deviceProtocol.delete(path);
|
|
935
|
+
this.Log?.debug(`[NodeUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
936
|
+
}
|
|
914
937
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
path,
|
|
918
|
-
this.getOpenDevice(path),
|
|
919
|
-
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
920
|
-
{ waitIndefinitelyOnTimeout: !deadline }
|
|
921
|
-
);
|
|
922
|
-
const packet = deadline
|
|
923
|
-
? await this.withProtocolReadTimeout(
|
|
924
|
-
path,
|
|
925
|
-
transferIn,
|
|
926
|
-
Math.max(deadline - Date.now(), 1),
|
|
927
|
-
'V2'
|
|
928
|
-
)
|
|
929
|
-
: await transferIn;
|
|
930
|
-
const bytes = new Uint8Array(
|
|
931
|
-
packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
|
|
932
|
-
);
|
|
933
|
-
try {
|
|
934
|
-
frame = assembler.push(bytes);
|
|
935
|
-
} catch (error) {
|
|
936
|
-
throw ERRORS.TypedError(
|
|
937
|
-
HardwareErrorCode.NetworkError,
|
|
938
|
-
error instanceof Error ? error.message : String(error)
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return frame;
|
|
938
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
939
|
+
return new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
943
940
|
}
|
|
944
941
|
|
|
945
942
|
private async callProtocolV2(
|
|
@@ -948,42 +945,7 @@ export default class NodeUsbTransport {
|
|
|
948
945
|
data: Record<string, unknown>,
|
|
949
946
|
options?: TransportCallOptions
|
|
950
947
|
) {
|
|
951
|
-
|
|
952
|
-
if (!this.messagesV2) {
|
|
953
|
-
throw ERRORS.TypedError(
|
|
954
|
-
HardwareErrorCode.TransportNotConfigured,
|
|
955
|
-
'Protocol V2 schema not configured'
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
if (!protocolV1Messages) {
|
|
959
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
let session = this.protocolV2Sessions.get(path);
|
|
963
|
-
if (!session) {
|
|
964
|
-
session = new ProtocolV2Session({
|
|
965
|
-
schemas: {
|
|
966
|
-
protocolV1: protocolV1Messages,
|
|
967
|
-
protocolV2: this.messagesV2,
|
|
968
|
-
},
|
|
969
|
-
router: PROTOCOL_V2_CHANNEL_USB,
|
|
970
|
-
writeFrame: (frame: Uint8Array) => this.writeProtocolV2Frame(path, frame),
|
|
971
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
972
|
-
logger: this.Log,
|
|
973
|
-
logPrefix: 'ProtocolV2 NodeUSB',
|
|
974
|
-
createTimeoutError: (messageName: string, timeoutMs: number) =>
|
|
975
|
-
new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
976
|
-
});
|
|
977
|
-
this.protocolV2Sessions.set(path, session);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
this.protocolV2ReadTimeouts.set(path, options?.timeoutMs);
|
|
981
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
982
|
-
try {
|
|
983
|
-
return await session.call(name, data, options);
|
|
984
|
-
} finally {
|
|
985
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
986
|
-
}
|
|
948
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
987
949
|
}
|
|
988
950
|
|
|
989
951
|
/**
|