@onekeyfe/hd-transport-web-device 1.2.2-alpha.100 → 1.2.2-alpha.3
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 +95 -11
- package/__tests__/webusb-protocol-cache.test.ts +32 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +8 -1
- package/dist/electron-ble-transport.d.ts +2 -0
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +98 -20
- package/dist/webusb.d.ts +1 -0
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +87 -10
- package/src/webusb.ts +37 -5
|
@@ -73,8 +73,26 @@ const protocolV2Schema = {
|
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
|
+
Failure: {
|
|
77
|
+
fields: {
|
|
78
|
+
code: {
|
|
79
|
+
type: 'FailureType',
|
|
80
|
+
id: 1,
|
|
81
|
+
},
|
|
82
|
+
message: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
id: 2,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
FailureType: {
|
|
89
|
+
values: {
|
|
90
|
+
Failure_ProcessError: 5,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
76
93
|
MessageType: {
|
|
77
94
|
values: {
|
|
95
|
+
MessageType_Failure: 3,
|
|
78
96
|
MessageType_ProtocolInfoRequest: 60200,
|
|
79
97
|
MessageType_ProtocolInfo: 60201,
|
|
80
98
|
MessageType_Ping: 60206,
|
|
@@ -327,7 +345,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
327
345
|
}
|
|
328
346
|
});
|
|
329
347
|
|
|
330
|
-
test('reconnects Protocol V1
|
|
348
|
+
test('reconnects a declared Protocol V1 device without probing again', async () => {
|
|
331
349
|
const device = { id: 'classic-id', name: 'OneKey Classic' };
|
|
332
350
|
const nobleBle = createNobleBle(device);
|
|
333
351
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -360,7 +378,10 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
360
378
|
uuid: device.id,
|
|
361
379
|
})
|
|
362
380
|
);
|
|
363
|
-
|
|
381
|
+
// The first acquire probes because the protocol is unknown; the second
|
|
382
|
+
// declares V1 and must send nothing at all, leaving the first frame on
|
|
383
|
+
// the link to Core — which is what carries the wallet session.
|
|
384
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
364
385
|
expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
|
|
365
386
|
expect(protocolV2Writer).not.toHaveBeenCalled();
|
|
366
387
|
} finally {
|
|
@@ -373,19 +394,13 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
373
394
|
const nobleBle = createNobleBle(device);
|
|
374
395
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
375
396
|
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
376
|
-
let writeCount = 0;
|
|
377
|
-
|
|
378
397
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
379
398
|
notificationHandler = handler;
|
|
380
399
|
return jest.fn();
|
|
381
400
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
386
|
-
}
|
|
387
|
-
return Promise.resolve();
|
|
388
|
-
});
|
|
401
|
+
// A declared V1 acquire writes nothing, so every write here belongs to the
|
|
402
|
+
// call under test and none of them is answered.
|
|
403
|
+
nobleBle.write.mockImplementation(() => Promise.resolve());
|
|
389
404
|
const bleTransport = configureTransport(nobleBle);
|
|
390
405
|
|
|
391
406
|
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
@@ -464,6 +479,41 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
464
479
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
465
480
|
});
|
|
466
481
|
|
|
482
|
+
test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
|
|
483
|
+
const device = { id: 'usb-priority-pro2-id', name: 'OneKey Pro 2' };
|
|
484
|
+
const nobleBle = createNobleBle(device);
|
|
485
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
486
|
+
|
|
487
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
488
|
+
notificationHandler = handler;
|
|
489
|
+
return jest.fn();
|
|
490
|
+
});
|
|
491
|
+
nobleBle.write.mockImplementation(() => {
|
|
492
|
+
const response = ProtocolV2.encodeFrame(
|
|
493
|
+
schemas,
|
|
494
|
+
'Failure',
|
|
495
|
+
{ code: 5, message: 'link disabled' },
|
|
496
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
497
|
+
);
|
|
498
|
+
const splitAt = 4;
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
notificationHandler?.(device.id, bytesToHex(response.subarray(0, splitAt)));
|
|
501
|
+
notificationHandler?.(device.id, bytesToHex(response.subarray(splitAt)));
|
|
502
|
+
}, 0);
|
|
503
|
+
return Promise.resolve();
|
|
504
|
+
});
|
|
505
|
+
const transport = configureTransport(nobleBle);
|
|
506
|
+
|
|
507
|
+
await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
|
|
508
|
+
name: 'ProtocolV2LinkDisabledError',
|
|
509
|
+
failureCode: 'Failure_ProcessError',
|
|
510
|
+
firmwareMessage: 'link disabled',
|
|
511
|
+
});
|
|
512
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
513
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
514
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
515
|
+
});
|
|
516
|
+
|
|
467
517
|
test('keeps a first expected Protocol V2 probe miss retryable', async () => {
|
|
468
518
|
const device = { id: 'first-v2-id', name: 'OneKey Pro 2' };
|
|
469
519
|
const nobleBle = createNobleBle(device);
|
|
@@ -500,6 +550,40 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
500
550
|
expect(transport.getProtocolType(device.id)).toBeUndefined();
|
|
501
551
|
});
|
|
502
552
|
|
|
553
|
+
test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
|
|
554
|
+
const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
|
|
555
|
+
const nobleBle = createNobleBle(device);
|
|
556
|
+
nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
|
|
557
|
+
const transport = configureTransport(nobleBle);
|
|
558
|
+
const probe = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
559
|
+
|
|
560
|
+
await expect(
|
|
561
|
+
transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
|
|
562
|
+
).rejects.toMatchObject({
|
|
563
|
+
errorCode: HardwareErrorCode.BleDeviceBondError,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
expect(probe).not.toHaveBeenCalled();
|
|
567
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
568
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test('keeps stale-bond subscribe mapping out of Protocol V1 acquire', async () => {
|
|
572
|
+
const device = { id: 'classic-v1-id', name: 'OneKey Classic' };
|
|
573
|
+
const nobleBle = createNobleBle(device);
|
|
574
|
+
nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
|
|
575
|
+
const transport = configureTransport(nobleBle);
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
579
|
+
throw new Error('Expected Protocol V1 acquire to fail');
|
|
580
|
+
} catch (error) {
|
|
581
|
+
expect(error).toBeInstanceOf(Error);
|
|
582
|
+
expect((error as Error).message).toBe('Encryption is insufficient');
|
|
583
|
+
expect((error as { errorCode?: unknown }).errorCode).toBeUndefined();
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
503
587
|
test('reports a stale bond when a previously confirmed Protocol V2 device stops responding', async () => {
|
|
504
588
|
const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
|
|
505
589
|
const nobleBle = createNobleBle(device);
|
|
@@ -69,6 +69,38 @@ describe('WebUsbTransport protocol probe cache', () => {
|
|
|
69
69
|
expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V1', undefined);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
test('acquire reuses a previously confirmed protocol during explicit no-probe recovery', async () => {
|
|
73
|
+
const webusb = buildAcquirableTransport();
|
|
74
|
+
const path = 'pro-webusb';
|
|
75
|
+
webusb.deviceProtocol.set(path, 'V1');
|
|
76
|
+
webusb.confirmedDeviceProtocols.set(path, 'V2');
|
|
77
|
+
webusb.markProtocolStale(path);
|
|
78
|
+
webusb.detectProtocol = jest.fn();
|
|
79
|
+
|
|
80
|
+
await expect(
|
|
81
|
+
webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
82
|
+
).resolves.toBe(path);
|
|
83
|
+
|
|
84
|
+
expect(webusb.detectProtocol).not.toHaveBeenCalled();
|
|
85
|
+
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
86
|
+
expect(webusb.staleProtocolPaths.has(path)).toBe(false);
|
|
87
|
+
expect(webusb.acquiredPaths.has(path)).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('acquire rejects no-probe recovery without a previously confirmed protocol', async () => {
|
|
91
|
+
const webusb = buildAcquirableTransport();
|
|
92
|
+
const path = 'pro-webusb';
|
|
93
|
+
webusb.detectProtocol = jest.fn();
|
|
94
|
+
|
|
95
|
+
await expect(
|
|
96
|
+
webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
97
|
+
).rejects.toThrow('previously confirmed protocol');
|
|
98
|
+
|
|
99
|
+
expect(webusb.detectProtocol).not.toHaveBeenCalled();
|
|
100
|
+
expect(webusb.deviceProtocol.has(path)).toBe(false);
|
|
101
|
+
expect(webusb.acquiredPaths.has(path)).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
72
104
|
test('forced protocol detection bypasses a valid cache and probes on the wire', async () => {
|
|
73
105
|
const webusb = buildAcquirableTransport();
|
|
74
106
|
const path = 'pro-webusb';
|
|
@@ -387,6 +387,13 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
387
387
|
webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
|
|
388
388
|
]);
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
// Each call keeps its own budget instead of inheriting the other's. The
|
|
391
|
+
// queued one is a deadline, so it arrives with whatever the first call left
|
|
392
|
+
// of its 25ms — asserting the exact remainder makes this fail on a loaded
|
|
393
|
+
// machine, which is timing, not behaviour.
|
|
394
|
+
expect(readTimeouts).toHaveLength(2);
|
|
395
|
+
expect(readTimeouts[0]).toBe(1_000);
|
|
396
|
+
expect(readTimeouts[1]).toBeGreaterThan(0);
|
|
397
|
+
expect(readTimeouts[1]).toBeLessThanOrEqual(25);
|
|
391
398
|
});
|
|
392
399
|
});
|
|
@@ -41,6 +41,7 @@ export default class ElectronBleTransport {
|
|
|
41
41
|
private hostDisconnectCleanup?;
|
|
42
42
|
private notificationTokens;
|
|
43
43
|
private nextNotificationToken;
|
|
44
|
+
private toStaleBondError;
|
|
44
45
|
private handleBluetoothError;
|
|
45
46
|
private cleanupDeviceState;
|
|
46
47
|
init(logger: any, emitter?: EventEmitter): void;
|
|
@@ -78,6 +79,7 @@ export default class ElectronBleTransport {
|
|
|
78
79
|
private createMtuSubscription;
|
|
79
80
|
private writeProtocolV2Frame;
|
|
80
81
|
private handleNotification;
|
|
82
|
+
private readProtocolV2LinkDisabledFailure;
|
|
81
83
|
private handleProtocolV2Notification;
|
|
82
84
|
private getProtocolV2FrameQueue;
|
|
83
85
|
private resolveProtocolV2Frame;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
19
19
|
private protocolV2SchemaSource;
|
|
20
20
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
21
21
|
private deviceProtocol;
|
|
22
|
+
/** Protocols previously confirmed by an active response for this transport instance. */
|
|
23
|
+
private confirmedDeviceProtocols;
|
|
22
24
|
private deviceProtocolHints;
|
|
23
25
|
/**
|
|
24
26
|
* Paths whose cached protocol must be re-probed on the next acquire (a USB
|
|
@@ -240,6 +242,7 @@ declare class ElectronBleTransport {
|
|
|
240
242
|
private hostDisconnectCleanup?;
|
|
241
243
|
private notificationTokens;
|
|
242
244
|
private nextNotificationToken;
|
|
245
|
+
private toStaleBondError;
|
|
243
246
|
private handleBluetoothError;
|
|
244
247
|
private cleanupDeviceState;
|
|
245
248
|
init(logger: any, emitter?: EventEmitter): void;
|
|
@@ -282,6 +285,7 @@ declare class ElectronBleTransport {
|
|
|
282
285
|
private createMtuSubscription;
|
|
283
286
|
private writeProtocolV2Frame;
|
|
284
287
|
private handleNotification;
|
|
288
|
+
private readProtocolV2LinkDisabledFailure;
|
|
285
289
|
private handleProtocolV2Notification;
|
|
286
290
|
private getProtocolV2FrameQueue;
|
|
287
291
|
private resolveProtocolV2Frame;
|
package/dist/index.js
CHANGED
|
@@ -79,6 +79,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
79
79
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
80
80
|
});
|
|
81
81
|
this.deviceProtocol = new Map();
|
|
82
|
+
this.confirmedDeviceProtocols = new Map();
|
|
82
83
|
this.deviceProtocolHints = new Map();
|
|
83
84
|
this.staleProtocolPaths = new Set();
|
|
84
85
|
this.acquiredPaths = new Set();
|
|
@@ -182,7 +183,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
182
183
|
});
|
|
183
184
|
}
|
|
184
185
|
acquire(input) {
|
|
185
|
-
var _a, _b, _c, _d, _e, _f;
|
|
186
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
186
187
|
return __awaiter(this, void 0, void 0, function* () {
|
|
187
188
|
if (!input.path)
|
|
188
189
|
return;
|
|
@@ -190,35 +191,53 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
190
191
|
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
191
192
|
yield this.closeOpenDevice(input.path);
|
|
192
193
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
193
|
-
if (input.
|
|
194
|
+
if (input.skipProtocolProbe) {
|
|
195
|
+
if (!input.expectedProtocol) {
|
|
196
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected protocol');
|
|
197
|
+
}
|
|
198
|
+
if (this.confirmedDeviceProtocols.get(input.path) !== input.expectedProtocol) {
|
|
199
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires a previously confirmed protocol for this WebUSB endpoint');
|
|
200
|
+
}
|
|
201
|
+
this.staleProtocolPaths.delete(input.path);
|
|
202
|
+
this.deviceProtocol.set(input.path, input.expectedProtocol);
|
|
203
|
+
const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
204
|
+
if (currentDevice) {
|
|
205
|
+
this.probedDeviceObjects.set(input.path, currentDevice);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (input.forceProtocolDetection) {
|
|
194
209
|
this.staleProtocolPaths.delete(input.path);
|
|
195
210
|
this.deviceProtocol.delete(input.path);
|
|
196
211
|
this.deviceProtocolHints.delete(input.path);
|
|
197
212
|
}
|
|
198
|
-
if (this.staleProtocolPaths.has(input.path)) {
|
|
213
|
+
if (!input.skipProtocolProbe && this.staleProtocolPaths.has(input.path)) {
|
|
199
214
|
this.staleProtocolPaths.delete(input.path);
|
|
200
215
|
this.deviceProtocol.delete(input.path);
|
|
201
216
|
}
|
|
202
|
-
if (this.deviceProtocol.has(input.path)) {
|
|
203
|
-
const currentDevice = (
|
|
217
|
+
if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
|
|
218
|
+
const currentDevice = (_c = this.deviceList.find(d => d.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
|
|
204
219
|
if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
|
|
205
220
|
this.deviceProtocol.delete(input.path);
|
|
206
221
|
}
|
|
207
222
|
}
|
|
208
223
|
const cachedProtocol = this.deviceProtocol.get(input.path);
|
|
209
|
-
if (
|
|
224
|
+
if (!input.skipProtocolProbe &&
|
|
225
|
+
cachedProtocol &&
|
|
226
|
+
input.expectedProtocol &&
|
|
227
|
+
cachedProtocol !== input.expectedProtocol) {
|
|
210
228
|
this.deviceProtocol.delete(input.path);
|
|
211
229
|
}
|
|
212
230
|
if (!this.deviceProtocol.has(input.path)) {
|
|
213
|
-
const usbDevice = (
|
|
231
|
+
const usbDevice = (_d = this.deviceList.find(device => device.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
|
|
214
232
|
const protocolHint = input.expectedProtocol
|
|
215
233
|
? undefined
|
|
216
|
-
: (
|
|
234
|
+
: (_f = (_e = input.protocolHint) !== null && _e !== void 0 ? _e : this.deviceProtocolHints.get(input.path)) !== null && _f !== void 0 ? _f : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
|
|
217
235
|
if (protocolHint) {
|
|
218
236
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
219
237
|
}
|
|
220
|
-
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
221
|
-
|
|
238
|
+
const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
239
|
+
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
240
|
+
const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
|
|
222
241
|
if (probedDevice) {
|
|
223
242
|
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
224
243
|
}
|
|
@@ -852,7 +871,29 @@ class ElectronBleTransport {
|
|
|
852
871
|
this.notificationTokens = new Map();
|
|
853
872
|
this.nextNotificationToken = 1;
|
|
854
873
|
}
|
|
855
|
-
|
|
874
|
+
toStaleBondError(error) {
|
|
875
|
+
var _a;
|
|
876
|
+
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
877
|
+
return error;
|
|
878
|
+
}
|
|
879
|
+
const errorMessage = error && typeof error === 'object' && 'message' in error
|
|
880
|
+
? String((_a = error.message) !== null && _a !== void 0 ? _a : '')
|
|
881
|
+
: String(error !== null && error !== void 0 ? error : '');
|
|
882
|
+
if (!hdShared.isBleStaleBondErrorText(errorMessage)) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
const normalizedErrorMessage = errorMessage.toLowerCase();
|
|
886
|
+
return hdShared.ERRORS.TypedError(normalizedErrorMessage.includes('peer removed pairing information')
|
|
887
|
+
? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
888
|
+
: hdShared.HardwareErrorCode.BleDeviceBondError, errorMessage);
|
|
889
|
+
}
|
|
890
|
+
handleBluetoothError(error, mapProtocolV2StaleBond = false) {
|
|
891
|
+
if (mapProtocolV2StaleBond) {
|
|
892
|
+
const staleBondError = this.toStaleBondError(error);
|
|
893
|
+
if (staleBondError) {
|
|
894
|
+
throw staleBondError;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
856
897
|
if (error && typeof error === 'object') {
|
|
857
898
|
if ('code' in error) {
|
|
858
899
|
if (error.code === hdShared.HardwareErrorCode.BlePoweredOff) {
|
|
@@ -983,6 +1024,9 @@ class ElectronBleTransport {
|
|
|
983
1024
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
984
1025
|
return __awaiter(this, void 0, void 0, function* () {
|
|
985
1026
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
1027
|
+
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
1028
|
+
? expectedProtocol === 'V2'
|
|
1029
|
+
: this.confirmedProtocolV2.has(uuid);
|
|
986
1030
|
if (!uuid) {
|
|
987
1031
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
988
1032
|
}
|
|
@@ -1014,7 +1058,7 @@ class ElectronBleTransport {
|
|
|
1014
1058
|
this.connectedDevices.add(uuid);
|
|
1015
1059
|
}
|
|
1016
1060
|
catch (error) {
|
|
1017
|
-
this.handleBluetoothError(error);
|
|
1061
|
+
this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
|
|
1018
1062
|
}
|
|
1019
1063
|
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
1020
1064
|
if (mtuCleanup) {
|
|
@@ -1022,7 +1066,12 @@ class ElectronBleTransport {
|
|
|
1022
1066
|
}
|
|
1023
1067
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1024
1068
|
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
1025
|
-
|
|
1069
|
+
try {
|
|
1070
|
+
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
|
|
1074
|
+
}
|
|
1026
1075
|
yield this.refreshBlePacketCapacity(uuid);
|
|
1027
1076
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
1028
1077
|
this.notificationCleanups.set(uuid, cleanup);
|
|
@@ -1128,12 +1177,9 @@ class ElectronBleTransport {
|
|
|
1128
1177
|
var _a, _b, _c;
|
|
1129
1178
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1130
1179
|
if (expectedProtocol === 'V1') {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
return 'V1';
|
|
1135
|
-
}
|
|
1136
|
-
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
1180
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1181
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected, no probe)`);
|
|
1182
|
+
return 'V1';
|
|
1137
1183
|
}
|
|
1138
1184
|
if (expectedProtocol === 'V2') {
|
|
1139
1185
|
if (yield this.probeProtocolV2(uuid)) {
|
|
@@ -1211,6 +1257,9 @@ class ElectronBleTransport {
|
|
|
1211
1257
|
catch (error) {
|
|
1212
1258
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1213
1259
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1260
|
+
if (transport.isProtocolV2LinkDisabledError(error)) {
|
|
1261
|
+
throw error;
|
|
1262
|
+
}
|
|
1214
1263
|
return false;
|
|
1215
1264
|
}
|
|
1216
1265
|
});
|
|
@@ -1233,6 +1282,7 @@ class ElectronBleTransport {
|
|
|
1233
1282
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1234
1283
|
this.resetProtocolV2Frames(uuid);
|
|
1235
1284
|
},
|
|
1285
|
+
shouldRethrow: error => hdShared.isBleStaleBondHardwareError(error) || transport.isProtocolV2LinkDisabledError(error),
|
|
1236
1286
|
});
|
|
1237
1287
|
if (!detected) {
|
|
1238
1288
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1247,7 +1297,16 @@ class ElectronBleTransport {
|
|
|
1247
1297
|
if (!nobleBle) {
|
|
1248
1298
|
throw new Error('Noble BLE API not available');
|
|
1249
1299
|
}
|
|
1250
|
-
|
|
1300
|
+
try {
|
|
1301
|
+
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1302
|
+
}
|
|
1303
|
+
catch (error) {
|
|
1304
|
+
const staleBondError = this.toStaleBondError(error);
|
|
1305
|
+
if (staleBondError) {
|
|
1306
|
+
throw staleBondError;
|
|
1307
|
+
}
|
|
1308
|
+
throw error;
|
|
1309
|
+
}
|
|
1251
1310
|
});
|
|
1252
1311
|
}
|
|
1253
1312
|
refreshBlePacketCapacity(uuid) {
|
|
@@ -1316,8 +1375,27 @@ class ElectronBleTransport {
|
|
|
1316
1375
|
this.handleProtocolV2Notification(deviceId, hexData);
|
|
1317
1376
|
return;
|
|
1318
1377
|
}
|
|
1378
|
+
const linkDisabledError = this.readProtocolV2LinkDisabledFailure(deviceId, hexData);
|
|
1379
|
+
if (linkDisabledError) {
|
|
1380
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1381
|
+
this.runPromise.reject(linkDisabledError);
|
|
1382
|
+
}
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1319
1385
|
this.handleProtocolV1Notification(deviceId, hexData);
|
|
1320
1386
|
}
|
|
1387
|
+
readProtocolV2LinkDisabledFailure(deviceId, hexData) {
|
|
1388
|
+
if (!this._messages || !this._messagesV2)
|
|
1389
|
+
return undefined;
|
|
1390
|
+
const assembler = this.v2Assemblers.get(deviceId);
|
|
1391
|
+
if (!assembler)
|
|
1392
|
+
return undefined;
|
|
1393
|
+
return transport.detectProtocolV2LinkDisabledError({
|
|
1394
|
+
schemas: { protocolV1: this._messages, protocolV2: this._messagesV2 },
|
|
1395
|
+
assembler,
|
|
1396
|
+
bytes: transport.hexToBytes(hexData),
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1321
1399
|
handleProtocolV2Notification(deviceId, hexData) {
|
|
1322
1400
|
var _a;
|
|
1323
1401
|
try {
|
package/dist/webusb.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
13
13
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
14
14
|
private protocolV2SchemaSource;
|
|
15
15
|
private deviceProtocol;
|
|
16
|
+
private confirmedDeviceProtocols;
|
|
16
17
|
private deviceProtocolHints;
|
|
17
18
|
private staleProtocolPaths;
|
|
18
19
|
private acquiredPaths;
|
package/dist/webusb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAuBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAuCD,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;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.3",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"lint:fix": "eslint . --fix"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.3",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.2-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.2-alpha.3",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "4efa66df8851607586d0ae6a431a63404beff51b"
|
|
33
33
|
}
|
|
@@ -6,7 +6,9 @@ import transport, {
|
|
|
6
6
|
ProtocolV2LinkManager,
|
|
7
7
|
TRANSPORT_EVENT,
|
|
8
8
|
bytesToHex,
|
|
9
|
+
detectProtocolV2LinkDisabledError,
|
|
9
10
|
hexToBytes,
|
|
11
|
+
isProtocolV2LinkDisabledError,
|
|
10
12
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
11
13
|
writeProtocolV2BleFrame,
|
|
12
14
|
} from '@onekeyfe/hd-transport';
|
|
@@ -16,6 +18,8 @@ import {
|
|
|
16
18
|
HardwareErrorCode,
|
|
17
19
|
HardwareErrorCodeMessage,
|
|
18
20
|
createDeferred,
|
|
21
|
+
isBleStaleBondErrorText,
|
|
22
|
+
isBleStaleBondHardwareError,
|
|
19
23
|
isHeaderChunk,
|
|
20
24
|
} from '@onekeyfe/hd-shared';
|
|
21
25
|
|
|
@@ -159,7 +163,33 @@ export default class ElectronBleTransport {
|
|
|
159
163
|
|
|
160
164
|
private nextNotificationToken = 1;
|
|
161
165
|
|
|
162
|
-
private
|
|
166
|
+
private toStaleBondError(error: unknown): Error | null {
|
|
167
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
168
|
+
return error as Error;
|
|
169
|
+
}
|
|
170
|
+
const errorMessage =
|
|
171
|
+
error && typeof error === 'object' && 'message' in error
|
|
172
|
+
? String((error as { message?: unknown }).message ?? '')
|
|
173
|
+
: String(error ?? '');
|
|
174
|
+
if (!isBleStaleBondErrorText(errorMessage)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const normalizedErrorMessage = errorMessage.toLowerCase();
|
|
178
|
+
return ERRORS.TypedError(
|
|
179
|
+
normalizedErrorMessage.includes('peer removed pairing information')
|
|
180
|
+
? HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
181
|
+
: HardwareErrorCode.BleDeviceBondError,
|
|
182
|
+
errorMessage
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private handleBluetoothError(error: any, mapProtocolV2StaleBond = false): never {
|
|
187
|
+
if (mapProtocolV2StaleBond) {
|
|
188
|
+
const staleBondError = this.toStaleBondError(error);
|
|
189
|
+
if (staleBondError) {
|
|
190
|
+
throw staleBondError;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
163
193
|
if (error && typeof error === 'object') {
|
|
164
194
|
if ('code' in error) {
|
|
165
195
|
if (error.code === HardwareErrorCode.BlePoweredOff) {
|
|
@@ -320,6 +350,9 @@ export default class ElectronBleTransport {
|
|
|
320
350
|
|
|
321
351
|
async acquire(input: BleAcquireInput) {
|
|
322
352
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
353
|
+
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
354
|
+
? expectedProtocol === 'V2'
|
|
355
|
+
: this.confirmedProtocolV2.has(uuid);
|
|
323
356
|
|
|
324
357
|
if (!uuid) {
|
|
325
358
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
@@ -356,7 +389,7 @@ export default class ElectronBleTransport {
|
|
|
356
389
|
await window.desktopApi.nobleBle.connect(uuid);
|
|
357
390
|
this.connectedDevices.add(uuid);
|
|
358
391
|
} catch (error) {
|
|
359
|
-
this.handleBluetoothError(error);
|
|
392
|
+
this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
|
|
360
393
|
}
|
|
361
394
|
|
|
362
395
|
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
@@ -367,7 +400,11 @@ export default class ElectronBleTransport {
|
|
|
367
400
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
368
401
|
this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
369
402
|
|
|
370
|
-
|
|
403
|
+
try {
|
|
404
|
+
await window.desktopApi.nobleBle.subscribe(uuid);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
|
|
407
|
+
}
|
|
371
408
|
await this.refreshBlePacketCapacity(uuid);
|
|
372
409
|
|
|
373
410
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -487,13 +524,20 @@ export default class ElectronBleTransport {
|
|
|
487
524
|
expectedProtocol?: ProtocolType,
|
|
488
525
|
protocolHint?: ProtocolType
|
|
489
526
|
): Promise<ProtocolType> {
|
|
527
|
+
// A declared V1 is taken at face value, as the React Native transport
|
|
528
|
+
// already does on iOS: the caller reads the protocol off its own device
|
|
529
|
+
// record, so probing re-asks a question that is already answered and adds a
|
|
530
|
+
// round trip to every cold connect. It also fails in a way that costs the
|
|
531
|
+
// session: a device whose protocol session has stalled ignores the probe
|
|
532
|
+
// frame, and the timeout became a protocol-mismatch error that stopped Core
|
|
533
|
+
// from ever sending Initialize — the one frame such a device still answers,
|
|
534
|
+
// and how it gets revived. A declared V2 keeps probing, matching iOS, so a
|
|
535
|
+
// USB-priority "link disabled" surfaces here rather than as an unmapped
|
|
536
|
+
// error later. An undeclared protocol still goes through full detection.
|
|
490
537
|
if (expectedProtocol === 'V1') {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return 'V1';
|
|
495
|
-
}
|
|
496
|
-
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
538
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
539
|
+
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected, no probe)`);
|
|
540
|
+
return 'V1';
|
|
497
541
|
}
|
|
498
542
|
|
|
499
543
|
if (expectedProtocol === 'V2') {
|
|
@@ -588,6 +632,9 @@ export default class ElectronBleTransport {
|
|
|
588
632
|
} catch (error) {
|
|
589
633
|
this.clearProbeProtocol(uuid, 'V1');
|
|
590
634
|
this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
635
|
+
if (isProtocolV2LinkDisabledError(error)) {
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
591
638
|
return false;
|
|
592
639
|
}
|
|
593
640
|
}
|
|
@@ -608,6 +655,8 @@ export default class ElectronBleTransport {
|
|
|
608
655
|
this.v2Assemblers.get(uuid)?.reset();
|
|
609
656
|
this.resetProtocolV2Frames(uuid);
|
|
610
657
|
},
|
|
658
|
+
shouldRethrow: error =>
|
|
659
|
+
isBleStaleBondHardwareError(error) || isProtocolV2LinkDisabledError(error),
|
|
611
660
|
});
|
|
612
661
|
if (!detected) {
|
|
613
662
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -621,7 +670,15 @@ export default class ElectronBleTransport {
|
|
|
621
670
|
throw new Error('Noble BLE API not available');
|
|
622
671
|
}
|
|
623
672
|
|
|
624
|
-
|
|
673
|
+
try {
|
|
674
|
+
await nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
675
|
+
} catch (error) {
|
|
676
|
+
const staleBondError = this.toStaleBondError(error);
|
|
677
|
+
if (staleBondError) {
|
|
678
|
+
throw staleBondError;
|
|
679
|
+
}
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
625
682
|
}
|
|
626
683
|
|
|
627
684
|
private async refreshBlePacketCapacity(uuid: string): Promise<void> {
|
|
@@ -695,9 +752,29 @@ export default class ElectronBleTransport {
|
|
|
695
752
|
this.handleProtocolV2Notification(deviceId, hexData);
|
|
696
753
|
return;
|
|
697
754
|
}
|
|
755
|
+
const linkDisabledError = this.readProtocolV2LinkDisabledFailure(deviceId, hexData);
|
|
756
|
+
if (linkDisabledError) {
|
|
757
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
758
|
+
this.runPromise.reject(linkDisabledError);
|
|
759
|
+
}
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
698
762
|
this.handleProtocolV1Notification(deviceId, hexData);
|
|
699
763
|
}
|
|
700
764
|
|
|
765
|
+
private readProtocolV2LinkDisabledFailure(deviceId: string, hexData: string) {
|
|
766
|
+
if (!this._messages || !this._messagesV2) return undefined;
|
|
767
|
+
|
|
768
|
+
const assembler = this.v2Assemblers.get(deviceId);
|
|
769
|
+
if (!assembler) return undefined;
|
|
770
|
+
|
|
771
|
+
return detectProtocolV2LinkDisabledError({
|
|
772
|
+
schemas: { protocolV1: this._messages, protocolV2: this._messagesV2 },
|
|
773
|
+
assembler,
|
|
774
|
+
bytes: hexToBytes(hexData),
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
701
778
|
private handleProtocolV2Notification(deviceId: string, hexData: string): void {
|
|
702
779
|
try {
|
|
703
780
|
const bytes = hexToBytes(hexData);
|
package/src/webusb.ts
CHANGED
|
@@ -107,6 +107,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
107
107
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
108
108
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
109
109
|
|
|
110
|
+
/** Protocols previously confirmed by an active response for this transport instance. */
|
|
111
|
+
private confirmedDeviceProtocols: Map<string, ProtocolType> = new Map();
|
|
112
|
+
|
|
110
113
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
111
114
|
|
|
112
115
|
/**
|
|
@@ -309,7 +312,26 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
309
312
|
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
310
313
|
await this.closeOpenDevice(input.path);
|
|
311
314
|
await this.connect(input.path ?? '', true);
|
|
312
|
-
if (input.
|
|
315
|
+
if (input.skipProtocolProbe) {
|
|
316
|
+
if (!input.expectedProtocol) {
|
|
317
|
+
throw ERRORS.TypedError(
|
|
318
|
+
HardwareErrorCode.RuntimeError,
|
|
319
|
+
'skipProtocolProbe requires an expected protocol'
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (this.confirmedDeviceProtocols.get(input.path) !== input.expectedProtocol) {
|
|
323
|
+
throw ERRORS.TypedError(
|
|
324
|
+
HardwareErrorCode.RuntimeError,
|
|
325
|
+
'skipProtocolProbe requires a previously confirmed protocol for this WebUSB endpoint'
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
this.staleProtocolPaths.delete(input.path);
|
|
329
|
+
this.deviceProtocol.set(input.path, input.expectedProtocol);
|
|
330
|
+
const currentDevice = this.deviceList.find(d => d.path === input.path)?.device;
|
|
331
|
+
if (currentDevice) {
|
|
332
|
+
this.probedDeviceObjects.set(input.path, currentDevice);
|
|
333
|
+
}
|
|
334
|
+
} else if (input.forceProtocolDetection) {
|
|
313
335
|
// Explicit recovery/discovery (e.g. detectDeviceConnectProtocol) must
|
|
314
336
|
// probe on the wire regardless of any cached result — the cached
|
|
315
337
|
// binding may be exactly what the caller is trying to recover from.
|
|
@@ -317,13 +339,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
317
339
|
this.deviceProtocol.delete(input.path);
|
|
318
340
|
this.deviceProtocolHints.delete(input.path);
|
|
319
341
|
}
|
|
320
|
-
if (this.staleProtocolPaths.has(input.path)) {
|
|
342
|
+
if (!input.skipProtocolProbe && this.staleProtocolPaths.has(input.path)) {
|
|
321
343
|
// The device disconnected (possibly rebooting into another mode) since
|
|
322
344
|
// the protocol was probed — drop the cache so it is re-probed below.
|
|
323
345
|
this.staleProtocolPaths.delete(input.path);
|
|
324
346
|
this.deviceProtocol.delete(input.path);
|
|
325
347
|
}
|
|
326
|
-
if (this.deviceProtocol.has(input.path)) {
|
|
348
|
+
if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
|
|
327
349
|
const currentDevice = this.deviceList.find(d => d.path === input.path)?.device;
|
|
328
350
|
if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
|
|
329
351
|
// The OS re-enumerated the device since the probe (a replug/reboot we
|
|
@@ -333,7 +355,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
333
355
|
}
|
|
334
356
|
}
|
|
335
357
|
const cachedProtocol = this.deviceProtocol.get(input.path);
|
|
336
|
-
if (
|
|
358
|
+
if (
|
|
359
|
+
!input.skipProtocolProbe &&
|
|
360
|
+
cachedProtocol &&
|
|
361
|
+
input.expectedProtocol &&
|
|
362
|
+
cachedProtocol !== input.expectedProtocol
|
|
363
|
+
) {
|
|
337
364
|
// The caller expects a different protocol than the cached probe result;
|
|
338
365
|
// the cache is stale — drop it and re-probe on the wire below.
|
|
339
366
|
this.deviceProtocol.delete(input.path);
|
|
@@ -348,7 +375,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
348
375
|
if (protocolHint) {
|
|
349
376
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
350
377
|
}
|
|
351
|
-
await this.detectProtocol(
|
|
378
|
+
const detectedProtocol = await this.detectProtocol(
|
|
379
|
+
input.path,
|
|
380
|
+
input.expectedProtocol,
|
|
381
|
+
protocolHint
|
|
382
|
+
);
|
|
383
|
+
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
352
384
|
const probedDevice = this.deviceList.find(d => d.path === input.path)?.device;
|
|
353
385
|
if (probedDevice) {
|
|
354
386
|
this.probedDeviceObjects.set(input.path, probedDevice);
|