@onekeyfe/hd-transport-web-device 1.2.0-alpha.32 → 1.2.0-alpha.34
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 -3
- package/__tests__/webusb-protocol-v2-timeout.test.ts +50 -1
- package/dist/electron-ble-transport.d.ts +2 -1
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +78 -54
- 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 +54 -23
- package/src/webusb.ts +36 -28
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
|
|
2
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
|
|
5
5
|
import ElectronBleTransport from '../src/electron-ble-transport';
|
|
@@ -11,6 +11,9 @@ const protocolV1Schema = {
|
|
|
11
11
|
Initialize: {
|
|
12
12
|
fields: {},
|
|
13
13
|
},
|
|
14
|
+
GetFeatures: {
|
|
15
|
+
fields: {},
|
|
16
|
+
},
|
|
14
17
|
Success: {
|
|
15
18
|
fields: {
|
|
16
19
|
message: {
|
|
@@ -23,6 +26,7 @@ const protocolV1Schema = {
|
|
|
23
26
|
values: {
|
|
24
27
|
MessageType_Initialize: 1,
|
|
25
28
|
MessageType_Success: 2,
|
|
29
|
+
MessageType_GetFeatures: 55,
|
|
26
30
|
},
|
|
27
31
|
},
|
|
28
32
|
},
|
|
@@ -238,7 +242,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
238
242
|
}
|
|
239
243
|
});
|
|
240
244
|
|
|
241
|
-
test('
|
|
245
|
+
test('reconnects Protocol V1 with a non-destructive GetFeatures probe', async () => {
|
|
242
246
|
const device = { id: 'classic-id', name: 'OneKey Classic' };
|
|
243
247
|
const nobleBle = createNobleBle(device);
|
|
244
248
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -252,7 +256,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
252
256
|
return jest.fn();
|
|
253
257
|
});
|
|
254
258
|
nobleBle.write.mockImplementation(() => {
|
|
255
|
-
//
|
|
259
|
+
// The first write is the V1 GetFeatures probe; answer with a V1 Success response.
|
|
256
260
|
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
257
261
|
return Promise.resolve();
|
|
258
262
|
});
|
|
@@ -266,12 +270,100 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
266
270
|
})
|
|
267
271
|
);
|
|
268
272
|
expect(transport.getProtocolType(device.id)).toBe('V1');
|
|
273
|
+
await expect(transport.acquire({ uuid: device.id, expectedProtocol: 'V1' })).resolves.toEqual(
|
|
274
|
+
expect.objectContaining({
|
|
275
|
+
uuid: device.id,
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
279
|
+
expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
|
|
269
280
|
expect(protocolV2Writer).not.toHaveBeenCalled();
|
|
270
281
|
} finally {
|
|
271
282
|
await transport.release(device.id);
|
|
272
283
|
}
|
|
273
284
|
});
|
|
274
285
|
|
|
286
|
+
test('invalidates and disconnects a Protocol V1 link after a response timeout', async () => {
|
|
287
|
+
const device = { id: 'classic-timeout-id', name: 'OneKey Classic' };
|
|
288
|
+
const nobleBle = createNobleBle(device);
|
|
289
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
290
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
291
|
+
let writeCount = 0;
|
|
292
|
+
|
|
293
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
294
|
+
notificationHandler = handler;
|
|
295
|
+
return jest.fn();
|
|
296
|
+
});
|
|
297
|
+
nobleBle.write.mockImplementation(() => {
|
|
298
|
+
writeCount += 1;
|
|
299
|
+
if (writeCount === 1) {
|
|
300
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
301
|
+
}
|
|
302
|
+
return Promise.resolve();
|
|
303
|
+
});
|
|
304
|
+
const bleTransport = configureTransport(nobleBle);
|
|
305
|
+
|
|
306
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
307
|
+
await expect(
|
|
308
|
+
bleTransport.call(device.id, 'Initialize', {}, { timeoutMs: 5 })
|
|
309
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleTimeoutError });
|
|
310
|
+
|
|
311
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
312
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
313
|
+
expect(bleTransport.getProtocolType(device.id)).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('keeps another device V2 reader when force-cleaning a V1 call', async () => {
|
|
317
|
+
const device = { id: 'classic-force-clean-id', name: 'OneKey Classic' };
|
|
318
|
+
const nobleBle = createNobleBle(device);
|
|
319
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
320
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
321
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
322
|
+
notificationHandler = handler;
|
|
323
|
+
return jest.fn();
|
|
324
|
+
});
|
|
325
|
+
nobleBle.write.mockImplementation(() => {
|
|
326
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
327
|
+
return Promise.resolve();
|
|
328
|
+
});
|
|
329
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
330
|
+
const activeV1Call = createDeferred<string>();
|
|
331
|
+
const otherDeviceReader = createDeferred<Uint8Array>();
|
|
332
|
+
activeV1Call.promise.catch(() => undefined);
|
|
333
|
+
otherDeviceReader.promise.catch(() => undefined);
|
|
334
|
+
bleTransport.runPromise = activeV1Call;
|
|
335
|
+
bleTransport.v2FramePromises.set('device-b', otherDeviceReader);
|
|
336
|
+
|
|
337
|
+
await bleTransport.acquire({
|
|
338
|
+
uuid: device.id,
|
|
339
|
+
expectedProtocol: 'V1',
|
|
340
|
+
forceCleanRunPromise: true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(bleTransport.v2FramePromises.get('device-b')).toBe(otherDeviceReader);
|
|
344
|
+
await bleTransport.release(device.id);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('rejects a pending V2 reader when its device frame state resets', async () => {
|
|
348
|
+
const nobleBle = createNobleBle();
|
|
349
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
350
|
+
const reader = createDeferred<Uint8Array>();
|
|
351
|
+
bleTransport.v2FramePromises.set('device-a', reader);
|
|
352
|
+
const result = Promise.race([
|
|
353
|
+
reader.promise.then(
|
|
354
|
+
() => 'resolved',
|
|
355
|
+
() => 'rejected'
|
|
356
|
+
),
|
|
357
|
+
new Promise(resolve => {
|
|
358
|
+
setTimeout(() => resolve('pending'), 20);
|
|
359
|
+
}),
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
bleTransport.resetProtocolV2Frames('device-a');
|
|
363
|
+
|
|
364
|
+
await expect(result).resolves.toBe('rejected');
|
|
365
|
+
});
|
|
366
|
+
|
|
275
367
|
test('throws when both protocol probes fail', async () => {
|
|
276
368
|
const device = { id: 'dead-device-id', name: 'Unknown Device' };
|
|
277
369
|
const nobleBle = createNobleBle(device);
|
|
@@ -96,6 +96,24 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
96
96
|
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test('allows legacy WebUSB Initialize up to the Node USB probe timeout', async () => {
|
|
100
|
+
const webusb = new WebUsbTransport() as any;
|
|
101
|
+
const path = 'pro-webusb';
|
|
102
|
+
webusb.messages = {};
|
|
103
|
+
webusb.callProtocolV1 = jest.fn().mockResolvedValue({});
|
|
104
|
+
|
|
105
|
+
await expect(webusb.probeProtocolV1(path)).resolves.toBe(true);
|
|
106
|
+
|
|
107
|
+
expect(webusb.callProtocolV1).toHaveBeenCalledWith(
|
|
108
|
+
path,
|
|
109
|
+
'Initialize',
|
|
110
|
+
{},
|
|
111
|
+
{
|
|
112
|
+
timeoutMs: 5000,
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
99
117
|
test('retries an expected Protocol V2 probe once after resetting the connection', async () => {
|
|
100
118
|
const webusb = new WebUsbTransport() as any;
|
|
101
119
|
const path = 'pro2-webusb';
|
|
@@ -117,6 +135,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
117
135
|
webusb.probeProtocolV1 = jest.fn();
|
|
118
136
|
webusb.probeProtocolV2 = jest.fn().mockResolvedValue(false);
|
|
119
137
|
webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
138
|
+
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
120
139
|
|
|
121
140
|
await expect(webusb.detectProtocol(path, 'V2')).rejects.toThrow(
|
|
122
141
|
'Protocol V2 probe timeout after 2 attempts'
|
|
@@ -124,10 +143,40 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
124
143
|
|
|
125
144
|
expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
|
|
126
145
|
expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
|
|
127
|
-
expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(
|
|
146
|
+
expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(webusb.closeConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
128
148
|
expect(webusb.deviceProtocol.has(path)).toBe(false);
|
|
129
149
|
});
|
|
130
150
|
|
|
151
|
+
test('closes the reopened device when acquire exhausts the expected Protocol V2 probe', async () => {
|
|
152
|
+
const webusb = new WebUsbTransport() as any;
|
|
153
|
+
const path = 'pro2-webusb';
|
|
154
|
+
const device = {
|
|
155
|
+
opened: false,
|
|
156
|
+
releaseInterface: jest.fn().mockResolvedValue(undefined),
|
|
157
|
+
close: jest.fn().mockImplementation(() => {
|
|
158
|
+
device.opened = false;
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
}),
|
|
161
|
+
};
|
|
162
|
+
webusb.deviceList = [{ path, device }];
|
|
163
|
+
webusb.Log = { debug: jest.fn() };
|
|
164
|
+
webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
|
|
165
|
+
webusb.connect = jest.fn().mockImplementation(() => {
|
|
166
|
+
device.opened = true;
|
|
167
|
+
return Promise.resolve();
|
|
168
|
+
});
|
|
169
|
+
webusb.detectProtocol = jest.fn().mockRejectedValue(new Error('terminal probe failure'));
|
|
170
|
+
|
|
171
|
+
await expect(webusb.acquire({ path, expectedProtocol: 'V2' })).rejects.toThrow(
|
|
172
|
+
'terminal probe failure'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(device.releaseInterface).toHaveBeenCalledTimes(1);
|
|
176
|
+
expect(device.close).toHaveBeenCalledTimes(1);
|
|
177
|
+
expect(device.opened).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
131
180
|
test('invalidates and resets the cached connection before another call can start', async () => {
|
|
132
181
|
const webusb = new WebUsbTransport() as any;
|
|
133
182
|
const path = 'pro2-webusb';
|
|
@@ -12,6 +12,7 @@ export type BleAcquireInput = {
|
|
|
12
12
|
uuid: string;
|
|
13
13
|
forceCleanRunPromise?: boolean;
|
|
14
14
|
expectedProtocol?: ProtocolType;
|
|
15
|
+
protocolHint?: ProtocolType;
|
|
15
16
|
};
|
|
16
17
|
export default class ElectronBleTransport {
|
|
17
18
|
private _messages;
|
|
@@ -20,6 +21,7 @@ export default class ElectronBleTransport {
|
|
|
20
21
|
name: string;
|
|
21
22
|
configured: boolean;
|
|
22
23
|
runPromise: Deferred<Uint8Array | string> | null;
|
|
24
|
+
private runPromiseDeviceId;
|
|
23
25
|
Log?: any;
|
|
24
26
|
emitter?: EventEmitter;
|
|
25
27
|
private connectedDevices;
|
|
@@ -68,7 +70,6 @@ export default class ElectronBleTransport {
|
|
|
68
70
|
private handleProtocolV2Notification;
|
|
69
71
|
private getProtocolV2FrameQueue;
|
|
70
72
|
private resolveProtocolV2Frame;
|
|
71
|
-
private rejectAllProtocolV2Frames;
|
|
72
73
|
private resetProtocolV2Frames;
|
|
73
74
|
private rejectProtocolV2Frames;
|
|
74
75
|
private readProtocolV2Frame;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,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;
|
|
1
|
+
{"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,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;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,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,OAAO,CAAC,kBAAkB,CAAuB;IAEjD,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;IA+B1B,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;;;;;;;;;;;IAsF9B,OAAO,CAAC,EAAE,EAAE,MAAM;YAUV,aAAa;IAe3B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YA8CjC,eAAe;YAkBf,eAAe;YAuBf,SAAS;IASvB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAqB9B,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;YAqFd,cAAc;IA0B5B,OAAO,CAAC,uBAAuB;IAyC/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -107,6 +107,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
107
107
|
private transferOutOnce;
|
|
108
108
|
private transferInWithRetry;
|
|
109
109
|
private transferInOnce;
|
|
110
|
+
private closeConnectionAfterProbe;
|
|
110
111
|
private resetConnectionAfterProbe;
|
|
111
112
|
private withProtocolReadTimeout;
|
|
112
113
|
private probeProtocolV1;
|
|
@@ -154,12 +155,13 @@ type BleAcquireInput = {
|
|
|
154
155
|
uuid: string;
|
|
155
156
|
forceCleanRunPromise?: boolean;
|
|
156
157
|
expectedProtocol?: ProtocolType;
|
|
158
|
+
protocolHint?: ProtocolType;
|
|
157
159
|
};
|
|
158
160
|
/**
|
|
159
161
|
* Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
|
|
160
162
|
*
|
|
161
163
|
* Protocol V1 devices continue using chunked packets. Protocol V2 is detected
|
|
162
|
-
* after a Protocol V1
|
|
164
|
+
* after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
|
|
163
165
|
*/
|
|
164
166
|
declare class ElectronBleTransport {
|
|
165
167
|
private _messages;
|
|
@@ -168,6 +170,7 @@ declare class ElectronBleTransport {
|
|
|
168
170
|
name: string;
|
|
169
171
|
configured: boolean;
|
|
170
172
|
runPromise: Deferred<Uint8Array | string> | null;
|
|
173
|
+
private runPromiseDeviceId;
|
|
171
174
|
Log?: any;
|
|
172
175
|
emitter?: EventEmitter;
|
|
173
176
|
private connectedDevices;
|
|
@@ -216,7 +219,6 @@ declare class ElectronBleTransport {
|
|
|
216
219
|
private handleProtocolV2Notification;
|
|
217
220
|
private getProtocolV2FrameQueue;
|
|
218
221
|
private resolveProtocolV2Frame;
|
|
219
|
-
private rejectAllProtocolV2Frames;
|
|
220
222
|
private resetProtocolV2Frames;
|
|
221
223
|
private rejectProtocolV2Frames;
|
|
222
224
|
private readProtocolV2Frame;
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,8 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
|
59
59
|
const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
60
60
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
61
61
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
62
|
-
const
|
|
62
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
63
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
63
64
|
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
64
65
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
65
66
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
@@ -155,7 +156,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
acquire(input) {
|
|
158
|
-
var _a, _b, _c;
|
|
159
|
+
var _a, _b, _c, _d;
|
|
159
160
|
return __awaiter(this, void 0, void 0, function* () {
|
|
160
161
|
if (!input.path)
|
|
161
162
|
return;
|
|
@@ -166,7 +167,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
166
167
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
167
168
|
const protocolHint = input.expectedProtocol
|
|
168
169
|
? undefined
|
|
169
|
-
: (_c = this.deviceProtocolHints.get(input.path)) !== null &&
|
|
170
|
+
: (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
|
|
170
171
|
if (protocolHint) {
|
|
171
172
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
172
173
|
}
|
|
@@ -175,6 +176,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
175
176
|
}
|
|
176
177
|
catch (e) {
|
|
177
178
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
179
|
+
yield this.closeOpenDevice(input.path);
|
|
178
180
|
throw e;
|
|
179
181
|
}
|
|
180
182
|
});
|
|
@@ -196,7 +198,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
196
198
|
this.deviceProtocol.set(path, 'V1');
|
|
197
199
|
return 'V1';
|
|
198
200
|
}
|
|
199
|
-
yield this.
|
|
201
|
+
yield this.closeConnectionAfterProbe(path);
|
|
200
202
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
201
203
|
}
|
|
202
204
|
if (expectedProtocol === 'V2') {
|
|
@@ -205,22 +207,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
205
207
|
this.deviceProtocol.set(path, 'V2');
|
|
206
208
|
return 'V2';
|
|
207
209
|
}
|
|
208
|
-
yield this.resetConnectionAfterProbe(path);
|
|
209
210
|
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
211
|
+
yield this.resetConnectionAfterProbe(path);
|
|
210
212
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 probe timed out, retrying ${attempt + 1}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`);
|
|
211
213
|
}
|
|
214
|
+
else {
|
|
215
|
+
yield this.closeConnectionAfterProbe(path);
|
|
216
|
+
}
|
|
212
217
|
}
|
|
213
218
|
this.deviceProtocol.delete(path);
|
|
214
219
|
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
215
220
|
}
|
|
216
221
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
217
|
-
for (const protocol of probeOrder) {
|
|
222
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
218
223
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
219
224
|
if (detected) {
|
|
220
225
|
this.deviceProtocol.set(path, protocol);
|
|
221
226
|
return protocol;
|
|
222
227
|
}
|
|
223
|
-
|
|
228
|
+
if (index < probeOrder.length - 1) {
|
|
229
|
+
yield this.resetConnectionAfterProbe(path);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
yield this.closeConnectionAfterProbe(path);
|
|
233
|
+
}
|
|
224
234
|
}
|
|
225
235
|
this.deviceProtocol.delete(path);
|
|
226
236
|
throw this.createProtocolDetectionError();
|
|
@@ -509,27 +519,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
509
519
|
return this.getTransferInData(result);
|
|
510
520
|
});
|
|
511
521
|
}
|
|
512
|
-
|
|
513
|
-
var _a;
|
|
522
|
+
closeConnectionAfterProbe(path) {
|
|
514
523
|
return __awaiter(this, void 0, void 0, function* () {
|
|
515
524
|
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
yield device.releaseInterface(ifaceNum);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
526
|
-
}
|
|
527
|
-
yield device.close();
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
catch (error) {
|
|
531
|
-
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
532
|
-
}
|
|
525
|
+
yield this.closeOpenDevice(path);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
resetConnectionAfterProbe(path) {
|
|
529
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
530
|
+
yield this.closeConnectionAfterProbe(path);
|
|
533
531
|
yield this.getConnectedDevices();
|
|
534
532
|
yield this.connect(path, false);
|
|
535
533
|
});
|
|
@@ -577,7 +575,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
577
575
|
return false;
|
|
578
576
|
}
|
|
579
577
|
try {
|
|
580
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
578
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
579
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
580
|
+
});
|
|
581
581
|
return true;
|
|
582
582
|
}
|
|
583
583
|
catch (_error) {
|
|
@@ -592,7 +592,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
592
592
|
}
|
|
593
593
|
return transport.probeProtocolV2({
|
|
594
594
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
595
|
-
timeoutMs:
|
|
595
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
596
596
|
logger: this.Log,
|
|
597
597
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
598
598
|
});
|
|
@@ -747,6 +747,7 @@ class ElectronBleTransport {
|
|
|
747
747
|
this.name = 'ElectronBleTransport';
|
|
748
748
|
this.configured = false;
|
|
749
749
|
this.runPromise = null;
|
|
750
|
+
this.runPromiseDeviceId = null;
|
|
750
751
|
this.connectedDevices = new Set();
|
|
751
752
|
this.deviceProtocol = new Map();
|
|
752
753
|
this.deviceProtocolHints = new Map();
|
|
@@ -819,6 +820,11 @@ class ElectronBleTransport {
|
|
|
819
820
|
this.v2Assemblers.delete(deviceId);
|
|
820
821
|
this.resetProtocolV2Frames(deviceId);
|
|
821
822
|
this.notificationTokens.delete(deviceId);
|
|
823
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
824
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
|
|
825
|
+
this.runPromise = null;
|
|
826
|
+
this.runPromiseDeviceId = null;
|
|
827
|
+
}
|
|
822
828
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
823
829
|
if (notifyCleanup) {
|
|
824
830
|
notifyCleanup();
|
|
@@ -887,7 +893,7 @@ class ElectronBleTransport {
|
|
|
887
893
|
});
|
|
888
894
|
}
|
|
889
895
|
acquire(input) {
|
|
890
|
-
var _a, _b, _c, _d, _e;
|
|
896
|
+
var _a, _b, _c, _d, _e, _f;
|
|
891
897
|
return __awaiter(this, void 0, void 0, function* () {
|
|
892
898
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
893
899
|
if (!uuid) {
|
|
@@ -899,8 +905,8 @@ class ElectronBleTransport {
|
|
|
899
905
|
if (forceCleanRunPromise && this.runPromise) {
|
|
900
906
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
901
907
|
this.runPromise.reject(error);
|
|
902
|
-
this.rejectAllProtocolV2Frames(error);
|
|
903
908
|
this.runPromise = null;
|
|
909
|
+
this.runPromiseDeviceId = null;
|
|
904
910
|
}
|
|
905
911
|
try {
|
|
906
912
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -912,7 +918,7 @@ class ElectronBleTransport {
|
|
|
912
918
|
}
|
|
913
919
|
const protocolHint = expectedProtocol
|
|
914
920
|
? undefined
|
|
915
|
-
: (_b = this.deviceProtocolHints.get(uuid)) !== null &&
|
|
921
|
+
: (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
|
|
916
922
|
if (protocolHint) {
|
|
917
923
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
918
924
|
}
|
|
@@ -928,6 +934,7 @@ class ElectronBleTransport {
|
|
|
928
934
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
929
935
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
930
936
|
this.notificationCleanups.set(uuid, cleanup);
|
|
937
|
+
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
931
938
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
932
939
|
var _a;
|
|
933
940
|
if (disconnectedDevice.id === uuid) {
|
|
@@ -940,19 +947,18 @@ class ElectronBleTransport {
|
|
|
940
947
|
}
|
|
941
948
|
});
|
|
942
949
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
943
|
-
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
944
950
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
945
951
|
}
|
|
946
952
|
catch (error) {
|
|
947
|
-
(
|
|
953
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
|
|
948
954
|
try {
|
|
949
|
-
if (((
|
|
955
|
+
if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
950
956
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
951
957
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
952
958
|
}
|
|
953
959
|
}
|
|
954
960
|
catch (cleanupError) {
|
|
955
|
-
(
|
|
961
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
956
962
|
}
|
|
957
963
|
this.cleanupDeviceState(uuid);
|
|
958
964
|
throw error;
|
|
@@ -994,7 +1000,7 @@ class ElectronBleTransport {
|
|
|
994
1000
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
995
1001
|
}
|
|
996
1002
|
createProtocolDetectionError() {
|
|
997
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1003
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
998
1004
|
}
|
|
999
1005
|
clearProbeProtocol(uuid, protocol) {
|
|
1000
1006
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1052,16 +1058,17 @@ class ElectronBleTransport {
|
|
|
1052
1058
|
});
|
|
1053
1059
|
}
|
|
1054
1060
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1055
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1061
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1056
1062
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1057
1063
|
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1058
1064
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1059
1065
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1060
1066
|
this.resetProtocolV2Frames(uuid);
|
|
1061
|
-
if (this.runPromise) {
|
|
1067
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1062
1068
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1063
1069
|
this.runPromise.reject(error);
|
|
1064
1070
|
this.runPromise = null;
|
|
1071
|
+
this.runPromiseDeviceId = null;
|
|
1065
1072
|
}
|
|
1066
1073
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1067
1074
|
if (notifyCleanup) {
|
|
@@ -1076,10 +1083,19 @@ class ElectronBleTransport {
|
|
|
1076
1083
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1077
1084
|
}
|
|
1078
1085
|
try {
|
|
1079
|
-
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.
|
|
1086
|
+
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
|
|
1080
1087
|
}
|
|
1081
1088
|
catch (error) {
|
|
1082
|
-
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE]
|
|
1089
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
1090
|
+
}
|
|
1091
|
+
this.connectedDevices.delete(uuid);
|
|
1092
|
+
try {
|
|
1093
|
+
yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
|
|
1094
|
+
this.connectedDevices.add(uuid);
|
|
1095
|
+
yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
|
|
1096
|
+
}
|
|
1097
|
+
catch (error) {
|
|
1098
|
+
(_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
1083
1099
|
throw error;
|
|
1084
1100
|
}
|
|
1085
1101
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1094,12 +1110,12 @@ class ElectronBleTransport {
|
|
|
1094
1110
|
}
|
|
1095
1111
|
try {
|
|
1096
1112
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1097
|
-
yield this.callProtocolV1(uuid, '
|
|
1113
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1098
1114
|
return true;
|
|
1099
1115
|
}
|
|
1100
1116
|
catch (error) {
|
|
1101
1117
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1102
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1118
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1103
1119
|
return false;
|
|
1104
1120
|
}
|
|
1105
1121
|
});
|
|
@@ -1160,7 +1176,7 @@ class ElectronBleTransport {
|
|
|
1160
1176
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1161
1177
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
1162
1178
|
}
|
|
1163
|
-
else if (this.runPromise) {
|
|
1179
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1164
1180
|
this.runPromise.reject(error);
|
|
1165
1181
|
}
|
|
1166
1182
|
return;
|
|
@@ -1212,16 +1228,8 @@ class ElectronBleTransport {
|
|
|
1212
1228
|
}
|
|
1213
1229
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1214
1230
|
}
|
|
1215
|
-
rejectAllProtocolV2Frames(error) {
|
|
1216
|
-
this.v2FrameQueues.clear();
|
|
1217
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
1218
|
-
framePromise.reject(error);
|
|
1219
|
-
}
|
|
1220
|
-
this.v2FramePromises.clear();
|
|
1221
|
-
}
|
|
1222
1231
|
resetProtocolV2Frames(uuid) {
|
|
1223
|
-
this.
|
|
1224
|
-
this.v2FramePromises.delete(uuid);
|
|
1232
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1225
1233
|
}
|
|
1226
1234
|
rejectProtocolV2Frames(uuid, error) {
|
|
1227
1235
|
this.v2FrameQueues.delete(uuid);
|
|
@@ -1254,12 +1262,15 @@ class ElectronBleTransport {
|
|
|
1254
1262
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1255
1263
|
if (result.error) {
|
|
1256
1264
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1257
|
-
if (this.runPromise) {
|
|
1265
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1258
1266
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1259
1267
|
}
|
|
1260
1268
|
return;
|
|
1261
1269
|
}
|
|
1262
|
-
if (result.isComplete &&
|
|
1270
|
+
if (result.isComplete &&
|
|
1271
|
+
result.completePacket &&
|
|
1272
|
+
this.runPromise &&
|
|
1273
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1263
1274
|
this.runPromise.resolve(result.completePacket);
|
|
1264
1275
|
}
|
|
1265
1276
|
}
|
|
@@ -1298,6 +1309,7 @@ class ElectronBleTransport {
|
|
|
1298
1309
|
const runPromise = hdShared.createDeferred();
|
|
1299
1310
|
runPromise.promise.catch(() => undefined);
|
|
1300
1311
|
this.runPromise = runPromise;
|
|
1312
|
+
this.runPromiseDeviceId = uuid;
|
|
1301
1313
|
const messages = this._messages;
|
|
1302
1314
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1303
1315
|
let timeout;
|
|
@@ -1336,6 +1348,17 @@ class ElectronBleTransport {
|
|
|
1336
1348
|
}
|
|
1337
1349
|
catch (e) {
|
|
1338
1350
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1351
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1352
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1353
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1354
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1355
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1356
|
+
this.notificationCleanups.delete(uuid);
|
|
1357
|
+
this.notificationTokens.delete(uuid);
|
|
1358
|
+
if (!isProbeTimeout) {
|
|
1359
|
+
yield this.releaseNative(uuid);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1339
1362
|
throw e;
|
|
1340
1363
|
}
|
|
1341
1364
|
finally {
|
|
@@ -1343,6 +1366,7 @@ class ElectronBleTransport {
|
|
|
1343
1366
|
clearTimeout(timeout);
|
|
1344
1367
|
if (this.runPromise === runPromise) {
|
|
1345
1368
|
this.runPromise = null;
|
|
1369
|
+
this.runPromiseDeviceId = null;
|
|
1346
1370
|
}
|
|
1347
1371
|
}
|
|
1348
1372
|
});
|
package/dist/webusb.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
50
50
|
private transferOutOnce;
|
|
51
51
|
private transferInWithRetry;
|
|
52
52
|
private transferInOnce;
|
|
53
|
+
private closeConnectionAfterProbe;
|
|
53
54
|
private resetConnectionAfterProbe;
|
|
54
55
|
private withProtocolReadTimeout;
|
|
55
56
|
private probeProtocolV1;
|
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,EAE3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.34",
|
|
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.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.34",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.34"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.34",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "07f10b4c9fab177da7c36d976ec18b075492b57f"
|
|
33
33
|
}
|
|
@@ -42,6 +42,7 @@ export type BleAcquireInput = {
|
|
|
42
42
|
uuid: string;
|
|
43
43
|
forceCleanRunPromise?: boolean;
|
|
44
44
|
expectedProtocol?: ProtocolType;
|
|
45
|
+
protocolHint?: ProtocolType;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
interface PacketProcessResult {
|
|
@@ -76,7 +77,7 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
|
76
77
|
* Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
|
|
77
78
|
*
|
|
78
79
|
* Protocol V1 devices continue using chunked packets. Protocol V2 is detected
|
|
79
|
-
* after a Protocol V1
|
|
80
|
+
* after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
|
|
80
81
|
*/
|
|
81
82
|
export default class ElectronBleTransport {
|
|
82
83
|
private _messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
@@ -91,6 +92,8 @@ export default class ElectronBleTransport {
|
|
|
91
92
|
|
|
92
93
|
runPromise: Deferred<Uint8Array | string> | null = null;
|
|
93
94
|
|
|
95
|
+
private runPromiseDeviceId: string | null = null;
|
|
96
|
+
|
|
94
97
|
Log?: any;
|
|
95
98
|
|
|
96
99
|
emitter?: EventEmitter;
|
|
@@ -181,6 +184,11 @@ export default class ElectronBleTransport {
|
|
|
181
184
|
this.v2Assemblers.delete(deviceId);
|
|
182
185
|
this.resetProtocolV2Frames(deviceId);
|
|
183
186
|
this.notificationTokens.delete(deviceId);
|
|
187
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
188
|
+
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
189
|
+
this.runPromise = null;
|
|
190
|
+
this.runPromiseDeviceId = null;
|
|
191
|
+
}
|
|
184
192
|
|
|
185
193
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
186
194
|
if (notifyCleanup) {
|
|
@@ -269,8 +277,8 @@ export default class ElectronBleTransport {
|
|
|
269
277
|
if (forceCleanRunPromise && this.runPromise) {
|
|
270
278
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
271
279
|
this.runPromise.reject(error);
|
|
272
|
-
this.rejectAllProtocolV2Frames(error);
|
|
273
280
|
this.runPromise = null;
|
|
281
|
+
this.runPromiseDeviceId = null;
|
|
274
282
|
}
|
|
275
283
|
|
|
276
284
|
try {
|
|
@@ -284,7 +292,9 @@ export default class ElectronBleTransport {
|
|
|
284
292
|
}
|
|
285
293
|
const protocolHint = expectedProtocol
|
|
286
294
|
? undefined
|
|
287
|
-
:
|
|
295
|
+
: input.protocolHint ??
|
|
296
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
297
|
+
inferProtocolHintFromDeviceName(device.name);
|
|
288
298
|
if (protocolHint) {
|
|
289
299
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
290
300
|
}
|
|
@@ -304,6 +314,8 @@ export default class ElectronBleTransport {
|
|
|
304
314
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
305
315
|
this.notificationCleanups.set(uuid, cleanup);
|
|
306
316
|
|
|
317
|
+
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
318
|
+
|
|
307
319
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
|
|
308
320
|
(disconnectedDevice: any) => {
|
|
309
321
|
if (disconnectedDevice.id === uuid) {
|
|
@@ -318,8 +330,6 @@ export default class ElectronBleTransport {
|
|
|
318
330
|
);
|
|
319
331
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
320
332
|
|
|
321
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
322
|
-
|
|
323
333
|
return {
|
|
324
334
|
...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
|
|
325
335
|
uuid,
|
|
@@ -374,7 +384,7 @@ export default class ElectronBleTransport {
|
|
|
374
384
|
private createProtocolDetectionError() {
|
|
375
385
|
return ERRORS.TypedError(
|
|
376
386
|
HardwareErrorCode.BleTimeoutError,
|
|
377
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
387
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
378
388
|
);
|
|
379
389
|
}
|
|
380
390
|
|
|
@@ -455,10 +465,11 @@ export default class ElectronBleTransport {
|
|
|
455
465
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
456
466
|
this.v2Assemblers.get(uuid)?.reset();
|
|
457
467
|
this.resetProtocolV2Frames(uuid);
|
|
458
|
-
if (this.runPromise) {
|
|
468
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
459
469
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
460
470
|
this.runPromise.reject(error);
|
|
461
471
|
this.runPromise = null;
|
|
472
|
+
this.runPromiseDeviceId = null;
|
|
462
473
|
}
|
|
463
474
|
|
|
464
475
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
@@ -474,9 +485,17 @@ export default class ElectronBleTransport {
|
|
|
474
485
|
this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
475
486
|
}
|
|
476
487
|
try {
|
|
488
|
+
await window.desktopApi?.nobleBle?.disconnect(uuid);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
this.Log?.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
491
|
+
}
|
|
492
|
+
this.connectedDevices.delete(uuid);
|
|
493
|
+
try {
|
|
494
|
+
await window.desktopApi?.nobleBle?.connect(uuid);
|
|
495
|
+
this.connectedDevices.add(uuid);
|
|
477
496
|
await window.desktopApi?.nobleBle?.subscribe(uuid);
|
|
478
497
|
} catch (error) {
|
|
479
|
-
this.Log?.debug(`[Electron BLE]
|
|
498
|
+
this.Log?.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
480
499
|
throw error;
|
|
481
500
|
}
|
|
482
501
|
|
|
@@ -491,11 +510,13 @@ export default class ElectronBleTransport {
|
|
|
491
510
|
|
|
492
511
|
try {
|
|
493
512
|
this.deviceProtocol.set(uuid, 'V1');
|
|
494
|
-
|
|
513
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
514
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
515
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
495
516
|
return true;
|
|
496
517
|
} catch (error) {
|
|
497
518
|
this.clearProbeProtocol(uuid, 'V1');
|
|
498
|
-
this.Log?.debug('[Electron BLE] Protocol V1
|
|
519
|
+
this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
499
520
|
return false;
|
|
500
521
|
}
|
|
501
522
|
}
|
|
@@ -557,7 +578,7 @@ export default class ElectronBleTransport {
|
|
|
557
578
|
const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
|
|
558
579
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
559
580
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
560
|
-
} else if (this.runPromise) {
|
|
581
|
+
} else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
561
582
|
this.runPromise.reject(error);
|
|
562
583
|
}
|
|
563
584
|
return;
|
|
@@ -612,17 +633,8 @@ export default class ElectronBleTransport {
|
|
|
612
633
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
613
634
|
}
|
|
614
635
|
|
|
615
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
616
|
-
this.v2FrameQueues.clear();
|
|
617
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
618
|
-
framePromise.reject(error);
|
|
619
|
-
}
|
|
620
|
-
this.v2FramePromises.clear();
|
|
621
|
-
}
|
|
622
|
-
|
|
623
636
|
private resetProtocolV2Frames(uuid: string) {
|
|
624
|
-
this.
|
|
625
|
-
this.v2FramePromises.delete(uuid);
|
|
637
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
626
638
|
}
|
|
627
639
|
|
|
628
640
|
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
@@ -656,13 +668,18 @@ export default class ElectronBleTransport {
|
|
|
656
668
|
|
|
657
669
|
if (result.error) {
|
|
658
670
|
this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
659
|
-
if (this.runPromise) {
|
|
671
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
660
672
|
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
|
|
661
673
|
}
|
|
662
674
|
return;
|
|
663
675
|
}
|
|
664
676
|
|
|
665
|
-
if (
|
|
677
|
+
if (
|
|
678
|
+
result.isComplete &&
|
|
679
|
+
result.completePacket &&
|
|
680
|
+
this.runPromise &&
|
|
681
|
+
this.runPromiseDeviceId === deviceId
|
|
682
|
+
) {
|
|
666
683
|
this.runPromise.resolve(result.completePacket);
|
|
667
684
|
}
|
|
668
685
|
}
|
|
@@ -716,6 +733,7 @@ export default class ElectronBleTransport {
|
|
|
716
733
|
const runPromise = createDeferred<Uint8Array | string>();
|
|
717
734
|
runPromise.promise.catch(() => undefined);
|
|
718
735
|
this.runPromise = runPromise;
|
|
736
|
+
this.runPromiseDeviceId = uuid;
|
|
719
737
|
const messages = this._messages;
|
|
720
738
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
721
739
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -760,11 +778,24 @@ export default class ElectronBleTransport {
|
|
|
760
778
|
return check.call(jsonData);
|
|
761
779
|
} catch (e) {
|
|
762
780
|
this.Log?.error('[Electron BLE] Protocol V1 call error:', e);
|
|
781
|
+
const isProbeTimeout =
|
|
782
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
783
|
+
if ((e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError) {
|
|
784
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
785
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
786
|
+
notifyCleanup?.();
|
|
787
|
+
this.notificationCleanups.delete(uuid);
|
|
788
|
+
this.notificationTokens.delete(uuid);
|
|
789
|
+
if (!isProbeTimeout) {
|
|
790
|
+
await this.releaseNative(uuid);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
763
793
|
throw e;
|
|
764
794
|
} finally {
|
|
765
795
|
if (timeout) clearTimeout(timeout);
|
|
766
796
|
if (this.runPromise === runPromise) {
|
|
767
797
|
this.runPromise = null;
|
|
798
|
+
this.runPromiseDeviceId = null;
|
|
768
799
|
}
|
|
769
800
|
}
|
|
770
801
|
}
|
package/src/webusb.ts
CHANGED
|
@@ -41,7 +41,11 @@ const REPORT_ID = PROTOCOL_V1_REPORT_ID;
|
|
|
41
41
|
const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
42
42
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
43
43
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
44
|
-
|
|
44
|
+
// Legacy devices can take longer to answer Initialize after a WebUSB reset.
|
|
45
|
+
// Keep this aligned with Node USB so a slow Protocol V1 response is not
|
|
46
|
+
// misreported as a protocol mismatch during acquire.
|
|
47
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
48
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
45
49
|
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
46
50
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
47
51
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
@@ -233,7 +237,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
233
237
|
.productName;
|
|
234
238
|
const protocolHint = input.expectedProtocol
|
|
235
239
|
? undefined
|
|
236
|
-
:
|
|
240
|
+
: input.protocolHint ??
|
|
241
|
+
this.deviceProtocolHints.get(input.path) ??
|
|
242
|
+
inferProtocolHintFromDeviceName(deviceName);
|
|
237
243
|
if (protocolHint) {
|
|
238
244
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
239
245
|
}
|
|
@@ -241,6 +247,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
241
247
|
return await Promise.resolve(input.path);
|
|
242
248
|
} catch (e) {
|
|
243
249
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
250
|
+
await this.closeOpenDevice(input.path);
|
|
244
251
|
throw e;
|
|
245
252
|
}
|
|
246
253
|
}
|
|
@@ -281,7 +288,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
281
288
|
this.deviceProtocol.set(path, 'V1');
|
|
282
289
|
return 'V1';
|
|
283
290
|
}
|
|
284
|
-
await this.
|
|
291
|
+
await this.closeConnectionAfterProbe(path);
|
|
285
292
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
286
293
|
}
|
|
287
294
|
|
|
@@ -291,13 +298,15 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
291
298
|
this.deviceProtocol.set(path, 'V2');
|
|
292
299
|
return 'V2';
|
|
293
300
|
}
|
|
294
|
-
await this.resetConnectionAfterProbe(path);
|
|
295
301
|
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
302
|
+
await this.resetConnectionAfterProbe(path);
|
|
296
303
|
this.Log?.debug(
|
|
297
304
|
`[WebUsbTransport] Protocol V2 probe timed out, retrying ${
|
|
298
305
|
attempt + 1
|
|
299
306
|
}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
|
|
300
307
|
);
|
|
308
|
+
} else {
|
|
309
|
+
await this.closeConnectionAfterProbe(path);
|
|
301
310
|
}
|
|
302
311
|
}
|
|
303
312
|
this.deviceProtocol.delete(path);
|
|
@@ -312,17 +321,21 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
312
321
|
const probeOrder: ProtocolType[] =
|
|
313
322
|
protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
314
323
|
|
|
315
|
-
for (const protocol of probeOrder) {
|
|
324
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
316
325
|
const detected =
|
|
317
326
|
protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
|
|
318
327
|
if (detected) {
|
|
319
328
|
this.deviceProtocol.set(path, protocol);
|
|
320
329
|
return protocol;
|
|
321
330
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
331
|
+
if (index < probeOrder.length - 1) {
|
|
332
|
+
// A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
|
|
333
|
+
// reopening the device guarantees the next protocol probe cannot consume a
|
|
334
|
+
// late response from the previous protocol generation.
|
|
335
|
+
await this.resetConnectionAfterProbe(path);
|
|
336
|
+
} else {
|
|
337
|
+
await this.closeConnectionAfterProbe(path);
|
|
338
|
+
}
|
|
326
339
|
}
|
|
327
340
|
|
|
328
341
|
this.deviceProtocol.delete(path);
|
|
@@ -645,25 +658,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
645
658
|
return this.getTransferInData(result);
|
|
646
659
|
}
|
|
647
660
|
|
|
648
|
-
private async
|
|
661
|
+
private async closeConnectionAfterProbe(path: string) {
|
|
649
662
|
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
663
|
+
await this.closeOpenDevice(path);
|
|
664
|
+
}
|
|
650
665
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (device.opened) {
|
|
654
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
655
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
656
|
-
try {
|
|
657
|
-
await device.releaseInterface(ifaceNum);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
660
|
-
}
|
|
661
|
-
await device.close();
|
|
662
|
-
}
|
|
663
|
-
} catch (error) {
|
|
664
|
-
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
665
|
-
}
|
|
666
|
-
|
|
666
|
+
private async resetConnectionAfterProbe(path: string) {
|
|
667
|
+
await this.closeConnectionAfterProbe(path);
|
|
667
668
|
await this.getConnectedDevices();
|
|
668
669
|
await this.connect(path, false);
|
|
669
670
|
}
|
|
@@ -716,7 +717,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
716
717
|
}
|
|
717
718
|
|
|
718
719
|
try {
|
|
719
|
-
await this.callProtocolV1(
|
|
720
|
+
await this.callProtocolV1(
|
|
721
|
+
path,
|
|
722
|
+
'Initialize',
|
|
723
|
+
{},
|
|
724
|
+
{
|
|
725
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
726
|
+
}
|
|
727
|
+
);
|
|
720
728
|
return true;
|
|
721
729
|
} catch (_error) {
|
|
722
730
|
return false;
|
|
@@ -730,7 +738,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
730
738
|
|
|
731
739
|
return probeProtocolV2Helper({
|
|
732
740
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
733
|
-
timeoutMs:
|
|
741
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
734
742
|
logger: this.Log,
|
|
735
743
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
736
744
|
});
|