@onekeyfe/hd-transport-web-device 1.2.0-alpha.23 → 1.2.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/electron-ble-transport.test.ts +102 -2
- package/__tests__/webusb-protocol-v2-timeout.test.ts +110 -42
- package/dist/electron-ble-transport.d.ts +1 -0
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +12 -9
- package/dist/index.js +87 -108
- package/dist/webusb.d.ts +12 -7
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +16 -10
- package/src/webusb.ts +83 -116
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
|
|
2
2
|
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
import EventEmitter from 'events';
|
|
3
4
|
|
|
4
5
|
import ElectronBleTransport from '../src/electron-ble-transport';
|
|
5
6
|
|
|
@@ -111,7 +112,10 @@ const createNobleBle = (device = { id: 'flaky-pro2-id', name: 'Unknown BLE Devic
|
|
|
111
112
|
),
|
|
112
113
|
});
|
|
113
114
|
|
|
114
|
-
const configureTransport = (
|
|
115
|
+
const configureTransport = (
|
|
116
|
+
nobleBle: ReturnType<typeof createNobleBle>,
|
|
117
|
+
emitter?: EventEmitter
|
|
118
|
+
) => {
|
|
115
119
|
(global as any).window = {
|
|
116
120
|
desktopApi: {
|
|
117
121
|
nobleBle,
|
|
@@ -119,7 +123,7 @@ const configureTransport = (nobleBle: ReturnType<typeof createNobleBle>) => {
|
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
const transport = new ElectronBleTransport();
|
|
122
|
-
transport.init(createLogger());
|
|
126
|
+
transport.init(createLogger(), emitter);
|
|
123
127
|
transport.configure(protocolV1Schema);
|
|
124
128
|
transport.configureProtocolV2(protocolV2Schema);
|
|
125
129
|
return transport;
|
|
@@ -131,6 +135,54 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
131
135
|
jest.clearAllMocks();
|
|
132
136
|
});
|
|
133
137
|
|
|
138
|
+
test('keeps raw BLE lifecycle payloads off the public device event channel', async () => {
|
|
139
|
+
const device = { id: 'lifecycle-pro2-id', name: 'OneKey Pro 2' };
|
|
140
|
+
const nobleBle = createNobleBle(device);
|
|
141
|
+
const emitter = new EventEmitter();
|
|
142
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
143
|
+
let disconnectHandler: ((device: { id: string; name: string | null }) => void) | undefined;
|
|
144
|
+
let responseSeq = 0;
|
|
145
|
+
|
|
146
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
147
|
+
notificationHandler = handler;
|
|
148
|
+
return jest.fn();
|
|
149
|
+
});
|
|
150
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
151
|
+
disconnectHandler = handler;
|
|
152
|
+
return jest.fn();
|
|
153
|
+
});
|
|
154
|
+
nobleBle.write.mockImplementation(() => {
|
|
155
|
+
responseSeq += 1;
|
|
156
|
+
const response = ProtocolV2.encodeFrame(
|
|
157
|
+
schemas,
|
|
158
|
+
'Success',
|
|
159
|
+
{ message: 'ok' },
|
|
160
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
161
|
+
);
|
|
162
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
163
|
+
return Promise.resolve();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const publicConnect = jest.fn();
|
|
167
|
+
const publicDisconnect = jest.fn();
|
|
168
|
+
const transportDisconnect = jest.fn();
|
|
169
|
+
emitter.on('device-connect', publicConnect);
|
|
170
|
+
emitter.on('device-disconnect', publicDisconnect);
|
|
171
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
172
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
173
|
+
|
|
174
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
175
|
+
disconnectHandler?.(device);
|
|
176
|
+
|
|
177
|
+
expect(publicConnect).not.toHaveBeenCalled();
|
|
178
|
+
expect(publicDisconnect).not.toHaveBeenCalled();
|
|
179
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
180
|
+
id: device.id,
|
|
181
|
+
connectId: device.id,
|
|
182
|
+
name: device.name,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
134
186
|
test('detects Protocol V2 after Protocol V1 probe timeout', async () => {
|
|
135
187
|
const device = { id: 'unknown-pro2-id', name: 'Unknown BLE Device' };
|
|
136
188
|
const nobleBle = createNobleBle(device);
|
|
@@ -341,4 +393,52 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
341
393
|
await transport.release(device.id);
|
|
342
394
|
}
|
|
343
395
|
});
|
|
396
|
+
|
|
397
|
+
test('preserves the active Protocol V2 link when the same schema is configured again', async () => {
|
|
398
|
+
const device = { id: 'stable-schema-pro2-id', name: 'OneKey Pro 2' };
|
|
399
|
+
const nobleBle = createNobleBle(device);
|
|
400
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
401
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
402
|
+
notificationHandler = handler;
|
|
403
|
+
return jest.fn();
|
|
404
|
+
});
|
|
405
|
+
let responseSeq = 0;
|
|
406
|
+
nobleBle.write.mockImplementation(() => {
|
|
407
|
+
responseSeq += 1;
|
|
408
|
+
const response = ProtocolV2.encodeFrame(
|
|
409
|
+
schemas,
|
|
410
|
+
'Success',
|
|
411
|
+
{ message: 'ok' },
|
|
412
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
413
|
+
);
|
|
414
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
415
|
+
return Promise.resolve();
|
|
416
|
+
});
|
|
417
|
+
const bleTransport = configureTransport(nobleBle);
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
421
|
+
const invalidateAllLinks = jest.spyOn(
|
|
422
|
+
(bleTransport as any).protocolV2Links,
|
|
423
|
+
'invalidateAllLinks'
|
|
424
|
+
);
|
|
425
|
+
bleTransport.configureProtocolV2(protocolV2Schema);
|
|
426
|
+
await new Promise<void>(resolve => {
|
|
427
|
+
setTimeout(resolve, 0);
|
|
428
|
+
});
|
|
429
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
430
|
+
await expect(
|
|
431
|
+
bleTransport.call(device.id, 'Ping', { message: 'same-schema' })
|
|
432
|
+
).resolves.toEqual({
|
|
433
|
+
type: 'Success',
|
|
434
|
+
message: { message: 'ok' },
|
|
435
|
+
});
|
|
436
|
+
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
437
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
438
|
+
);
|
|
439
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
440
|
+
} finally {
|
|
441
|
+
await bleTransport.release(device.id);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
344
444
|
});
|
|
@@ -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
|
|
|
@@ -80,20 +80,72 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
80
80
|
const path = 'pro2-webusb';
|
|
81
81
|
webusb.messages = transport.parseConfigure(schema);
|
|
82
82
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
83
|
-
webusb.
|
|
84
|
-
webusb.
|
|
85
|
-
webusb.
|
|
86
|
-
webusb.resetConnectionAfterProbe = jest.fn()
|
|
87
|
-
|
|
88
|
-
webusb.protocolV2Assemblers.get(path)?.reset();
|
|
89
|
-
});
|
|
83
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
84
|
+
webusb.readProtocolV2UsbPacket = jest.fn(() => new Promise<void>(() => {}));
|
|
85
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
86
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
87
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
90
88
|
|
|
91
89
|
await expect(
|
|
92
90
|
webusb.callProtocolV2(path, 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
|
|
93
91
|
).rejects.toThrow('timeout');
|
|
94
92
|
|
|
95
|
-
expect(webusb.
|
|
96
|
-
|
|
93
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
94
|
+
path,
|
|
95
|
+
expect.stringContaining('timeout')
|
|
96
|
+
);
|
|
97
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('does not reconnect inside a Protocol V2 frame read after a USB I/O failure', async () => {
|
|
101
|
+
const webusb = new WebUsbTransport() as any;
|
|
102
|
+
const path = 'pro2-webusb';
|
|
103
|
+
webusb.messages = transport.parseConfigure(schema);
|
|
104
|
+
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
105
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
106
|
+
webusb.readProtocolV2UsbPacket = jest
|
|
107
|
+
.fn()
|
|
108
|
+
.mockRejectedValue(new Error('NetworkError: transferIn device disconnected'));
|
|
109
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
110
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
111
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
112
|
+
|
|
113
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'read-error' })).rejects.toThrow(
|
|
114
|
+
'NetworkError'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
119
|
+
path,
|
|
120
|
+
expect.stringContaining('NetworkError')
|
|
121
|
+
);
|
|
122
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('rejects an active Protocol V2 read without reconnecting after release', async () => {
|
|
126
|
+
const webusb = new WebUsbTransport() as any;
|
|
127
|
+
const path = 'pro2-webusb';
|
|
128
|
+
let markReadStarted: () => void = () => undefined;
|
|
129
|
+
const readStarted = new Promise<void>(resolve => {
|
|
130
|
+
markReadStarted = resolve;
|
|
131
|
+
});
|
|
132
|
+
webusb.messages = transport.parseConfigure(schema);
|
|
133
|
+
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
134
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
135
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
|
|
136
|
+
markReadStarted();
|
|
137
|
+
return new Promise<void>(() => {});
|
|
138
|
+
});
|
|
139
|
+
webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
|
|
140
|
+
webusb.connect = jest.fn();
|
|
141
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
142
|
+
|
|
143
|
+
const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
|
|
144
|
+
await readStarted;
|
|
145
|
+
await webusb.release(path);
|
|
146
|
+
|
|
147
|
+
await expect(call).rejects.toThrow('WebUSB transport released');
|
|
148
|
+
expect(webusb.connect).not.toHaveBeenCalled();
|
|
97
149
|
});
|
|
98
150
|
|
|
99
151
|
test.each(['router', 'packet-source', 'ack-sequence', 'response-sequence', 'frame'] as const)(
|
|
@@ -103,32 +155,34 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
103
155
|
const path = 'pro2-webusb';
|
|
104
156
|
webusb.messages = transport.parseConfigure(schema);
|
|
105
157
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
106
|
-
webusb.
|
|
107
|
-
webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
|
|
158
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
108
159
|
const recoveredResponse = ProtocolV2.encodeFrame(
|
|
109
160
|
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
110
161
|
'Success',
|
|
111
162
|
{ message: 'recovered' },
|
|
112
163
|
{ seq: 1 }
|
|
113
164
|
);
|
|
114
|
-
webusb.
|
|
165
|
+
webusb.readProtocolV2UsbPacket = jest
|
|
115
166
|
.fn()
|
|
116
167
|
.mockRejectedValueOnce(
|
|
117
168
|
new ProtocolV2LinkError(code, `Protocol V2 ${code} validation failed`)
|
|
118
169
|
)
|
|
119
170
|
.mockResolvedValue(recoveredResponse);
|
|
120
|
-
webusb.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
171
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
172
|
+
webusb.resetConnectionAfterProbe = jest.fn();
|
|
173
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
124
174
|
|
|
125
175
|
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'mismatch' })).rejects.toThrow(
|
|
126
176
|
`${code} validation failed`
|
|
127
177
|
);
|
|
128
178
|
|
|
129
|
-
expect(webusb.
|
|
130
|
-
|
|
179
|
+
expect(webusb.resetProtocolV2UsbNativeLink).toHaveBeenCalledWith(
|
|
180
|
+
path,
|
|
181
|
+
expect.stringContaining(`${code} validation failed`)
|
|
182
|
+
);
|
|
183
|
+
expect(webusb.resetConnectionAfterProbe).not.toHaveBeenCalled();
|
|
131
184
|
|
|
185
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test reconnect');
|
|
132
186
|
await expect(
|
|
133
187
|
webusb.callProtocolV2(path, 'Ping', { message: 'after-reset' })
|
|
134
188
|
).resolves.toMatchObject({
|
|
@@ -141,39 +195,53 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
141
195
|
test('does not discard buffered Protocol V2 frames before each call', async () => {
|
|
142
196
|
const webusb = new WebUsbTransport() as any;
|
|
143
197
|
const path = 'pro2-webusb';
|
|
144
|
-
const assembler = new ProtocolV2FrameAssembler();
|
|
145
|
-
const reset = jest.spyOn(assembler, 'reset');
|
|
146
|
-
let responseSequence = 0;
|
|
147
198
|
webusb.messages = transport.parseConfigure(schema);
|
|
148
199
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
149
|
-
webusb.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
200
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
201
|
+
const firstResponse = ProtocolV2.encodeFrame(
|
|
202
|
+
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
203
|
+
'Success',
|
|
204
|
+
{ message: 'first' },
|
|
205
|
+
{ router: PROTOCOL_V2_CHANNEL_USB, seq: 1 }
|
|
206
|
+
);
|
|
207
|
+
const secondResponse = ProtocolV2.encodeFrame(
|
|
208
|
+
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
209
|
+
'Success',
|
|
210
|
+
{ message: 'second' },
|
|
211
|
+
{ router: PROTOCOL_V2_CHANNEL_USB, seq: 2 }
|
|
212
|
+
);
|
|
213
|
+
const coalescedResponses = new Uint8Array(firstResponse.length + secondResponse.length);
|
|
214
|
+
coalescedResponses.set(firstResponse);
|
|
215
|
+
coalescedResponses.set(secondResponse, firstResponse.length);
|
|
216
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockResolvedValue(coalescedResponses);
|
|
217
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
218
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
161
219
|
|
|
162
|
-
await webusb.callProtocolV2(path, 'Ping', { message: 'first' })
|
|
163
|
-
|
|
220
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'first' })).resolves.toMatchObject({
|
|
221
|
+
type: 'Success',
|
|
222
|
+
message: { message: 'first' },
|
|
223
|
+
});
|
|
224
|
+
await expect(webusb.callProtocolV2(path, 'Ping', { message: 'second' })).resolves.toMatchObject(
|
|
225
|
+
{
|
|
226
|
+
type: 'Success',
|
|
227
|
+
message: { message: 'second' },
|
|
228
|
+
}
|
|
229
|
+
);
|
|
164
230
|
|
|
165
|
-
expect(
|
|
231
|
+
expect(webusb.readProtocolV2UsbPacket).toHaveBeenCalledTimes(1);
|
|
166
232
|
});
|
|
167
233
|
|
|
168
234
|
test('keeps queued Protocol V2 read timeouts scoped to each call', async () => {
|
|
169
235
|
const webusb = new WebUsbTransport() as any;
|
|
170
236
|
const path = 'pro2-webusb';
|
|
171
237
|
let responseSequence = 0;
|
|
238
|
+
const readTimeouts: number[] = [];
|
|
172
239
|
webusb.messages = transport.parseConfigure(schema);
|
|
173
240
|
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
174
|
-
webusb.
|
|
175
|
-
webusb.
|
|
241
|
+
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
242
|
+
webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation((_path, context) => {
|
|
176
243
|
responseSequence += 1;
|
|
244
|
+
readTimeouts.push(context.timeoutMs);
|
|
177
245
|
return Promise.resolve(
|
|
178
246
|
ProtocolV2.encodeFrame(
|
|
179
247
|
{ protocolV1: webusb.messages, protocolV2: webusb.messagesV2 },
|
|
@@ -183,14 +251,14 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
183
251
|
)
|
|
184
252
|
);
|
|
185
253
|
});
|
|
254
|
+
webusb.resetProtocolV2UsbNativeLink = jest.fn().mockResolvedValue(undefined);
|
|
255
|
+
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
186
256
|
|
|
187
257
|
await Promise.all([
|
|
188
258
|
webusb.callProtocolV2(path, 'Ping', { message: 'long' }, { timeoutMs: 1_000 }),
|
|
189
259
|
webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
|
|
190
260
|
]);
|
|
191
261
|
|
|
192
|
-
expect(
|
|
193
|
-
webusb.receiveProtocolV2Frame.mock.calls.map(([, timeoutMs]: unknown[]) => timeoutMs)
|
|
194
|
-
).toEqual([1_000, 25]);
|
|
262
|
+
expect(readTimeouts).toEqual([1_000, 25]);
|
|
195
263
|
});
|
|
196
264
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAqBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC;AAoCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,SAAS,CAAsE;IAEvF,OAAO,CAAC,YAAY,CAAoD;IAExE,OAAO,CAAC,aAAa,CAAwC;IAE7D,OAAO,CAAC,eAAe,CAAgD;IAEvE,OAAO,CAAC,eAAe,CAmBpB;IAEH,OAAO,CAAC,oBAAoB,CAAsC;IAElE,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,kBAAkB,CAAkC;IAE5D,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IA0B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAgB7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IAoF9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAgBxB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAqCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,SAAS;IASvB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAgB9B,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;YAuEd,cAAc;IA0B5B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
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,13 @@ 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
19
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
20
20
|
private deviceProtocol;
|
|
21
21
|
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
22
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
29
23
|
private deviceEndpoints;
|
|
30
24
|
/**
|
|
@@ -46,6 +40,7 @@ declare class WebUsbTransport {
|
|
|
46
40
|
configurationId: number;
|
|
47
41
|
endpointId: number;
|
|
48
42
|
interfaceId: number;
|
|
43
|
+
constructor();
|
|
49
44
|
/**
|
|
50
45
|
* Initialize WebUSB transport
|
|
51
46
|
*/
|
|
@@ -120,6 +115,7 @@ declare class WebUsbTransport {
|
|
|
120
115
|
private transferOutWithRetry;
|
|
121
116
|
private transferOutOnce;
|
|
122
117
|
private transferInWithRetry;
|
|
118
|
+
private transferInOnce;
|
|
123
119
|
private resetConnectionAfterProbe;
|
|
124
120
|
private withProtocolReadTimeout;
|
|
125
121
|
private probeProtocolV1;
|
|
@@ -136,7 +132,6 @@ declare class WebUsbTransport {
|
|
|
136
132
|
* Decoding: Protocol V2 frame → messageTypeId + pb bytes → protobuf message
|
|
137
133
|
*/
|
|
138
134
|
private callProtocolV2;
|
|
139
|
-
private receiveProtocolV2Frame;
|
|
140
135
|
/**
|
|
141
136
|
* Receive data from device
|
|
142
137
|
*/
|
|
@@ -145,6 +140,13 @@ declare class WebUsbTransport {
|
|
|
145
140
|
* Release device
|
|
146
141
|
*/
|
|
147
142
|
release(path: string): Promise<void>;
|
|
143
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
|
|
144
|
+
protected getProtocolV2UsbLogger(): any;
|
|
145
|
+
protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
|
|
146
|
+
protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
|
|
147
|
+
protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
|
|
148
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
|
|
149
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
|
|
148
150
|
/**
|
|
149
151
|
* Expose the detected protocol type for a given device path.
|
|
150
152
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|
|
@@ -171,6 +173,7 @@ type BleAcquireInput = {
|
|
|
171
173
|
declare class ElectronBleTransport {
|
|
172
174
|
private _messages;
|
|
173
175
|
private _messagesV2;
|
|
176
|
+
private protocolV2SchemaConfiguration;
|
|
174
177
|
name: string;
|
|
175
178
|
configured: boolean;
|
|
176
179
|
runPromise: Deferred<Uint8Array | string> | null;
|
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;
|
|
@@ -97,7 +99,7 @@ class WebUsbTransport {
|
|
|
97
99
|
}
|
|
98
100
|
configureProtocolV2(signedData) {
|
|
99
101
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
100
|
-
this.
|
|
102
|
+
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
103
|
}
|
|
102
104
|
promptDeviceAccess() {
|
|
103
105
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -163,6 +165,7 @@ class WebUsbTransport {
|
|
|
163
165
|
if (!input.path)
|
|
164
166
|
return;
|
|
165
167
|
try {
|
|
168
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
166
169
|
yield this.closeOpenDevice(input.path);
|
|
167
170
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
168
171
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
@@ -289,7 +292,7 @@ class WebUsbTransport {
|
|
|
289
292
|
};
|
|
290
293
|
}
|
|
291
294
|
connectToDevice(path, first) {
|
|
292
|
-
var _a
|
|
295
|
+
var _a;
|
|
293
296
|
return __awaiter(this, void 0, void 0, function* () {
|
|
294
297
|
let device = yield this.findDevice(path);
|
|
295
298
|
if (!device.opened) {
|
|
@@ -313,33 +316,30 @@ class WebUsbTransport {
|
|
|
313
316
|
}
|
|
314
317
|
const endpoints = this.discoverEndpoints(device);
|
|
315
318
|
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
319
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
319
320
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
320
321
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
321
322
|
});
|
|
322
323
|
}
|
|
323
324
|
closeOpenDevice(path) {
|
|
324
|
-
var _a, _b, _c, _d
|
|
325
|
+
var _a, _b, _c, _d;
|
|
325
326
|
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;
|
|
327
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
328
328
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
329
329
|
return;
|
|
330
330
|
const endpoints = this.deviceEndpoints.get(path);
|
|
331
|
-
const ifaceNum = (
|
|
331
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
332
332
|
try {
|
|
333
333
|
yield current.releaseInterface(ifaceNum);
|
|
334
334
|
}
|
|
335
335
|
catch (error) {
|
|
336
|
-
(
|
|
336
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
337
337
|
}
|
|
338
338
|
try {
|
|
339
339
|
yield current.close();
|
|
340
340
|
}
|
|
341
341
|
catch (error) {
|
|
342
|
-
(
|
|
342
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
343
343
|
}
|
|
344
344
|
});
|
|
345
345
|
}
|
|
@@ -501,16 +501,28 @@ class WebUsbTransport {
|
|
|
501
501
|
throw lastError;
|
|
502
502
|
});
|
|
503
503
|
}
|
|
504
|
+
transferInOnce(path, length) {
|
|
505
|
+
var _a;
|
|
506
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
507
|
+
const device = yield this.findDevice(path);
|
|
508
|
+
if (!device.opened) {
|
|
509
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
510
|
+
}
|
|
511
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
512
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
513
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
514
|
+
return this.getTransferInData(result);
|
|
515
|
+
});
|
|
516
|
+
}
|
|
504
517
|
resetConnectionAfterProbe(path) {
|
|
505
|
-
var _a
|
|
518
|
+
var _a;
|
|
506
519
|
return __awaiter(this, void 0, void 0, function* () {
|
|
507
|
-
|
|
508
|
-
this.protocolV2Sessions.delete(path);
|
|
520
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
509
521
|
try {
|
|
510
522
|
const device = yield this.findDevice(path);
|
|
511
523
|
if (device.opened) {
|
|
512
524
|
const endpoints = this.deviceEndpoints.get(path);
|
|
513
|
-
const ifaceNum = (
|
|
525
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
514
526
|
try {
|
|
515
527
|
yield device.releaseInterface(ifaceNum);
|
|
516
528
|
}
|
|
@@ -584,7 +596,7 @@ class WebUsbTransport {
|
|
|
584
596
|
return false;
|
|
585
597
|
}
|
|
586
598
|
return transport.probeProtocolV2({
|
|
587
|
-
call: (name, data, options) => this.callProtocolV2(path, name, data, options
|
|
599
|
+
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
588
600
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
589
601
|
logger: this.Log,
|
|
590
602
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
@@ -634,74 +646,9 @@ class WebUsbTransport {
|
|
|
634
646
|
return check$1.call(jsonData);
|
|
635
647
|
});
|
|
636
648
|
}
|
|
637
|
-
callProtocolV2(path, name, data, options
|
|
638
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
639
|
-
const protocolV1Messages = this.messages;
|
|
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) {
|
|
649
|
+
callProtocolV2(path, name, data, options) {
|
|
685
650
|
return __awaiter(this, void 0, void 0, function* () {
|
|
686
|
-
|
|
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;
|
|
651
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
705
652
|
});
|
|
706
653
|
}
|
|
707
654
|
receiveData(path, timeoutMs) {
|
|
@@ -742,21 +689,50 @@ class WebUsbTransport {
|
|
|
742
689
|
});
|
|
743
690
|
}
|
|
744
691
|
release(path) {
|
|
745
|
-
var _a, _b;
|
|
746
692
|
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();
|
|
693
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
694
|
+
yield this.closeOpenDevice(path);
|
|
752
695
|
this.deviceProtocol.delete(path);
|
|
753
696
|
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
697
|
this.deviceEndpoints.delete(path);
|
|
758
698
|
});
|
|
759
699
|
}
|
|
700
|
+
getProtocolV2UsbSchemas() {
|
|
701
|
+
if (!this.messages || !this.messagesV2) {
|
|
702
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
protocolV1: this.messages,
|
|
706
|
+
protocolV2: this.messagesV2,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
getProtocolV2UsbLogger() {
|
|
710
|
+
return this.Log;
|
|
711
|
+
}
|
|
712
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
713
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
714
|
+
yield this.transferOutOnce(path, frame);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
718
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
719
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
720
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
724
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
725
|
+
yield this.closeOpenDevice(path);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
729
|
+
var _a;
|
|
730
|
+
this.deviceProtocol.delete(path);
|
|
731
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
732
|
+
}
|
|
733
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
734
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
735
|
+
}
|
|
760
736
|
getProtocolType(path) {
|
|
761
737
|
return this.deviceProtocol.get(path);
|
|
762
738
|
}
|
|
@@ -873,10 +849,18 @@ class ElectronBleTransport {
|
|
|
873
849
|
this.configured = true;
|
|
874
850
|
}
|
|
875
851
|
configureProtocolV2(signedData) {
|
|
852
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
853
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
876
857
|
this._messagesV2 = parseConfigure(signedData);
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
|
|
858
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
859
|
+
if (isReconfiguration) {
|
|
860
|
+
this.protocolV2Links
|
|
861
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
862
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
|
|
863
|
+
}
|
|
880
864
|
}
|
|
881
865
|
listen() {
|
|
882
866
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -908,7 +892,7 @@ class ElectronBleTransport {
|
|
|
908
892
|
});
|
|
909
893
|
}
|
|
910
894
|
acquire(input) {
|
|
911
|
-
var _a, _b, _c, _d, _e
|
|
895
|
+
var _a, _b, _c, _d, _e;
|
|
912
896
|
return __awaiter(this, void 0, void 0, function* () {
|
|
913
897
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
914
898
|
if (!uuid) {
|
|
@@ -953,7 +937,7 @@ class ElectronBleTransport {
|
|
|
953
937
|
var _a;
|
|
954
938
|
if (disconnectedDevice.id === uuid) {
|
|
955
939
|
this.cleanupDeviceState(uuid);
|
|
956
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(
|
|
940
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
957
941
|
name: disconnectedDevice.name,
|
|
958
942
|
id: disconnectedDevice.id,
|
|
959
943
|
connectId: disconnectedDevice.id,
|
|
@@ -962,23 +946,18 @@ class ElectronBleTransport {
|
|
|
962
946
|
});
|
|
963
947
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
964
948
|
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
965
|
-
(_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
|
|
966
|
-
name: device.name,
|
|
967
|
-
id: device.id,
|
|
968
|
-
connectId: device.id,
|
|
969
|
-
});
|
|
970
949
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
971
950
|
}
|
|
972
951
|
catch (error) {
|
|
973
|
-
(
|
|
952
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] acquire failed:', error);
|
|
974
953
|
try {
|
|
975
|
-
if (((
|
|
954
|
+
if (((_d = window.desktopApi) === null || _d === void 0 ? void 0 : _d.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
976
955
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
977
956
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
978
957
|
}
|
|
979
958
|
}
|
|
980
959
|
catch (cleanupError) {
|
|
981
|
-
(
|
|
960
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
982
961
|
}
|
|
983
962
|
this.cleanupDeviceState(uuid);
|
|
984
963
|
throw error;
|
package/dist/webusb.d.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
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
12
|
private deviceProtocol;
|
|
13
13
|
private deviceProtocolHints;
|
|
14
|
-
private protocolV2Assemblers;
|
|
15
|
-
private protocolV2Sessions;
|
|
16
|
-
private protocolV2Sequences;
|
|
17
14
|
private deviceEndpoints;
|
|
18
15
|
private mockSerialPaths;
|
|
19
16
|
private mockSerialCounter;
|
|
@@ -26,6 +23,7 @@ export default class WebUsbTransport {
|
|
|
26
23
|
configurationId: number;
|
|
27
24
|
endpointId: number;
|
|
28
25
|
interfaceId: number;
|
|
26
|
+
constructor();
|
|
29
27
|
init(logger: any): void;
|
|
30
28
|
configure(signedData: any): void;
|
|
31
29
|
configureProtocolV2(signedData: any): void;
|
|
@@ -53,6 +51,7 @@ export default class WebUsbTransport {
|
|
|
53
51
|
private transferOutWithRetry;
|
|
54
52
|
private transferOutOnce;
|
|
55
53
|
private transferInWithRetry;
|
|
54
|
+
private transferInOnce;
|
|
56
55
|
private resetConnectionAfterProbe;
|
|
57
56
|
private withProtocolReadTimeout;
|
|
58
57
|
private probeProtocolV1;
|
|
@@ -60,9 +59,15 @@ export default class WebUsbTransport {
|
|
|
60
59
|
call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
|
|
61
60
|
private callProtocolV1;
|
|
62
61
|
private callProtocolV2;
|
|
63
|
-
private receiveProtocolV2Frame;
|
|
64
62
|
receiveData(path: string, timeoutMs?: number): Promise<string>;
|
|
65
63
|
release(path: string): Promise<void>;
|
|
64
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
|
|
65
|
+
protected getProtocolV2UsbLogger(): any;
|
|
66
|
+
protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
|
|
67
|
+
protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
|
|
68
|
+
protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
|
|
69
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
|
|
70
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
|
|
66
71
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
67
72
|
}
|
|
68
73
|
//# 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":";AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAE3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAsBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAaD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC7E,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;IAGpE,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,eAAe,CAA2C;IAMlE,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;;IAa3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAY7B,kBAAkB;IAmBlB,SAAS;IAQf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAgCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA2BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IA6DtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;YAgCpC,eAAe;YAkBf,iBAAiB;IAezB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAiCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,cAAc;YAWd,yBAAyB;YAuBzB,uBAAuB;YA0CvB,eAAe;YAaf,eAAe;IAgBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA8BlB,cAAc;YAkCd,cAAc;IAYtB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAQ1B,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.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.25",
|
|
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.25",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.25"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.
|
|
27
|
+
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.25",
|
|
28
28
|
"@types/w3c-web-usb": "^1.0.6",
|
|
29
29
|
"@types/web-bluetooth": "^0.0.17"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "20bb98b8530c509299b9e109d2b5ada7c6a6d034"
|
|
32
32
|
}
|
|
@@ -3,6 +3,7 @@ import transport, {
|
|
|
3
3
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
4
4
|
ProtocolV2FrameAssembler,
|
|
5
5
|
ProtocolV2LinkManager,
|
|
6
|
+
TRANSPORT_EVENT,
|
|
6
7
|
bytesToHex,
|
|
7
8
|
hexToBytes,
|
|
8
9
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
@@ -76,6 +77,8 @@ export default class ElectronBleTransport {
|
|
|
76
77
|
|
|
77
78
|
private _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
78
79
|
|
|
80
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
81
|
+
|
|
79
82
|
name = 'ElectronBleTransport';
|
|
80
83
|
|
|
81
84
|
configured = false;
|
|
@@ -206,10 +209,19 @@ export default class ElectronBleTransport {
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
configureProtocolV2(signedData: any) {
|
|
212
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
213
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
209
218
|
this._messagesV2 = parseConfigure(signedData);
|
|
210
|
-
this.
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
220
|
+
if (isReconfiguration) {
|
|
221
|
+
this.protocolV2Links
|
|
222
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
223
|
+
.catch(error => this.Log?.debug('[Electron BLE] schema link cleanup failed:', error));
|
|
224
|
+
}
|
|
213
225
|
}
|
|
214
226
|
|
|
215
227
|
async listen() {
|
|
@@ -290,7 +302,7 @@ export default class ElectronBleTransport {
|
|
|
290
302
|
(disconnectedDevice: any) => {
|
|
291
303
|
if (disconnectedDevice.id === uuid) {
|
|
292
304
|
this.cleanupDeviceState(uuid);
|
|
293
|
-
this.emitter?.emit(
|
|
305
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
294
306
|
name: disconnectedDevice.name,
|
|
295
307
|
id: disconnectedDevice.id,
|
|
296
308
|
connectId: disconnectedDevice.id,
|
|
@@ -302,12 +314,6 @@ export default class ElectronBleTransport {
|
|
|
302
314
|
|
|
303
315
|
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
304
316
|
|
|
305
|
-
this.emitter?.emit('device-connect', {
|
|
306
|
-
name: device.name,
|
|
307
|
-
id: device.id,
|
|
308
|
-
connectId: device.id,
|
|
309
|
-
});
|
|
310
|
-
|
|
311
317
|
return {
|
|
312
318
|
...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
|
|
313
319
|
uuid,
|
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,7 +67,7 @@ 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. */
|
|
@@ -79,15 +78,6 @@ export default class WebUsbTransport {
|
|
|
79
78
|
|
|
80
79
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
81
80
|
|
|
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
81
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
92
82
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
93
83
|
|
|
@@ -121,6 +111,14 @@ export default class WebUsbTransport {
|
|
|
121
111
|
|
|
122
112
|
interfaceId = INTERFACE_ID;
|
|
123
113
|
|
|
114
|
+
constructor() {
|
|
115
|
+
super({
|
|
116
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
117
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
118
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
124
122
|
/**
|
|
125
123
|
* Initialize WebUSB transport
|
|
126
124
|
*/
|
|
@@ -151,7 +149,9 @@ export default class WebUsbTransport {
|
|
|
151
149
|
*/
|
|
152
150
|
configureProtocolV2(signedData: any) {
|
|
153
151
|
this.messagesV2 = parseConfigure(signedData);
|
|
154
|
-
this.
|
|
152
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
153
|
+
this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
|
|
154
|
+
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
@@ -240,6 +240,7 @@ export default class WebUsbTransport {
|
|
|
240
240
|
async acquire(input: AcquireInput) {
|
|
241
241
|
if (!input.path) return;
|
|
242
242
|
try {
|
|
243
|
+
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
243
244
|
await this.closeOpenDevice(input.path);
|
|
244
245
|
await this.connect(input.path ?? '', true);
|
|
245
246
|
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
@@ -447,16 +448,12 @@ export default class WebUsbTransport {
|
|
|
447
448
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
448
449
|
const endpoints = this.discoverEndpoints(device);
|
|
449
450
|
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
451
|
await device.claimInterface(endpoints.interfaceNumber);
|
|
454
452
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
455
453
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
456
454
|
}
|
|
457
455
|
|
|
458
456
|
private async closeOpenDevice(path: string) {
|
|
459
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
460
457
|
const current = this.deviceList.find(device => device.path === path)?.device;
|
|
461
458
|
if (!current?.opened) return;
|
|
462
459
|
|
|
@@ -651,9 +648,19 @@ export default class WebUsbTransport {
|
|
|
651
648
|
throw lastError;
|
|
652
649
|
}
|
|
653
650
|
|
|
651
|
+
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
652
|
+
const device = await this.findDevice(path);
|
|
653
|
+
if (!device.opened) {
|
|
654
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
655
|
+
}
|
|
656
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
657
|
+
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
658
|
+
const result = await device.transferIn(endpointIn, length);
|
|
659
|
+
return this.getTransferInData(result);
|
|
660
|
+
}
|
|
661
|
+
|
|
654
662
|
private async resetConnectionAfterProbe(path: string) {
|
|
655
|
-
this.
|
|
656
|
-
this.protocolV2Sessions.delete(path);
|
|
663
|
+
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
657
664
|
|
|
658
665
|
try {
|
|
659
666
|
const device = await this.findDevice(path);
|
|
@@ -736,7 +743,7 @@ export default class WebUsbTransport {
|
|
|
736
743
|
}
|
|
737
744
|
|
|
738
745
|
return probeProtocolV2Helper({
|
|
739
|
-
call: (name, data, options) => this.callProtocolV2(path, name, data, options
|
|
746
|
+
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
740
747
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
741
748
|
logger: this.Log,
|
|
742
749
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
@@ -818,93 +825,9 @@ export default class WebUsbTransport {
|
|
|
818
825
|
path: string,
|
|
819
826
|
name: string,
|
|
820
827
|
data: Record<string, unknown>,
|
|
821
|
-
options?: TransportCallOptions
|
|
822
|
-
resetOnError = true
|
|
828
|
+
options?: TransportCallOptions
|
|
823
829
|
) {
|
|
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;
|
|
830
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
908
831
|
}
|
|
909
832
|
|
|
910
833
|
/**
|
|
@@ -959,19 +882,63 @@ export default class WebUsbTransport {
|
|
|
959
882
|
* Release device
|
|
960
883
|
*/
|
|
961
884
|
async release(path: string) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
965
|
-
await device.releaseInterface(ifaceNum);
|
|
966
|
-
await device.close();
|
|
885
|
+
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
886
|
+
await this.closeOpenDevice(path);
|
|
967
887
|
this.deviceProtocol.delete(path);
|
|
968
888
|
this.deviceProtocolHints.delete(path);
|
|
969
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
970
|
-
this.protocolV2Assemblers.delete(path);
|
|
971
|
-
this.protocolV2Sessions.delete(path);
|
|
972
889
|
this.deviceEndpoints.delete(path);
|
|
973
890
|
}
|
|
974
891
|
|
|
892
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
893
|
+
if (!this.messages || !this.messagesV2) {
|
|
894
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
protocolV1: this.messages,
|
|
898
|
+
protocolV2: this.messagesV2,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
protected getProtocolV2UsbLogger() {
|
|
903
|
+
return this.Log;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
protected async writeProtocolV2UsbPacket(
|
|
907
|
+
path: string,
|
|
908
|
+
frame: Uint8Array,
|
|
909
|
+
_context: ProtocolV2CallContext
|
|
910
|
+
): Promise<void> {
|
|
911
|
+
await this.transferOutOnce(path, frame);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
protected async readProtocolV2UsbPacket(
|
|
915
|
+
path: string,
|
|
916
|
+
_context: ProtocolV2CallContext
|
|
917
|
+
): Promise<Uint8Array> {
|
|
918
|
+
const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
919
|
+
return new Uint8Array(
|
|
920
|
+
this.toArrayBuffer(
|
|
921
|
+
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
922
|
+
)
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
927
|
+
await this.closeOpenDevice(path);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
931
|
+
this.deviceProtocol.delete(path);
|
|
932
|
+
this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
936
|
+
return new ProtocolV2LinkError(
|
|
937
|
+
'response-timeout',
|
|
938
|
+
`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
975
942
|
/**
|
|
976
943
|
* Expose the detected protocol type for a given device path.
|
|
977
944
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|