@onekeyfe/hd-transport-web-device 1.2.0-alpha.33 → 1.2.0-alpha.35
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 +70 -2
- 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 +83 -67
- package/dist/transportLog.d.ts +1 -6
- package/dist/transportLog.d.ts.map +1 -1
- 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 +55 -24
- package/src/transportLog.ts +1 -11
- package/src/webusb.ts +38 -33
|
@@ -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);
|
|
@@ -3,7 +3,7 @@ import transport, {
|
|
|
3
3
|
ProtocolV2,
|
|
4
4
|
ProtocolV2LinkError,
|
|
5
5
|
} from '@onekeyfe/hd-transport';
|
|
6
|
-
import { ONEKEY_WEBUSB_FILTER } from '@onekeyfe/hd-shared';
|
|
6
|
+
import { HardwareErrorCode, ONEKEY_WEBUSB_FILTER } from '@onekeyfe/hd-shared';
|
|
7
7
|
|
|
8
8
|
import WebUsbTransport from '../src/webusb';
|
|
9
9
|
|
|
@@ -96,6 +96,43 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
96
96
|
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test('reports DeviceNotFound when automatic protocol detection exhausts both probes', async () => {
|
|
100
|
+
const webusb = new WebUsbTransport() as any;
|
|
101
|
+
const path = 'unresponsive-webusb';
|
|
102
|
+
webusb.probeProtocolV1 = jest.fn().mockResolvedValue(false);
|
|
103
|
+
webusb.probeProtocolV2 = jest.fn().mockResolvedValue(false);
|
|
104
|
+
webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
105
|
+
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
106
|
+
|
|
107
|
+
await expect(webusb.detectProtocol(path)).rejects.toMatchObject({
|
|
108
|
+
errorCode: HardwareErrorCode.DeviceNotFound,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(webusb.probeProtocolV1).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(webusb.closeConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(webusb.deviceProtocol.has(path)).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('allows legacy WebUSB Initialize up to the Node USB probe timeout', async () => {
|
|
119
|
+
const webusb = new WebUsbTransport() as any;
|
|
120
|
+
const path = 'pro-webusb';
|
|
121
|
+
webusb.messages = {};
|
|
122
|
+
webusb.callProtocolV1 = jest.fn().mockResolvedValue({});
|
|
123
|
+
|
|
124
|
+
await expect(webusb.probeProtocolV1(path)).resolves.toBe(true);
|
|
125
|
+
|
|
126
|
+
expect(webusb.callProtocolV1).toHaveBeenCalledWith(
|
|
127
|
+
path,
|
|
128
|
+
'Initialize',
|
|
129
|
+
{},
|
|
130
|
+
{
|
|
131
|
+
timeoutMs: 5000,
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
99
136
|
test('retries an expected Protocol V2 probe once after resetting the connection', async () => {
|
|
100
137
|
const webusb = new WebUsbTransport() as any;
|
|
101
138
|
const path = 'pro2-webusb';
|
|
@@ -117,6 +154,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
117
154
|
webusb.probeProtocolV1 = jest.fn();
|
|
118
155
|
webusb.probeProtocolV2 = jest.fn().mockResolvedValue(false);
|
|
119
156
|
webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
157
|
+
webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
|
|
120
158
|
|
|
121
159
|
await expect(webusb.detectProtocol(path, 'V2')).rejects.toThrow(
|
|
122
160
|
'Protocol V2 probe timeout after 2 attempts'
|
|
@@ -124,10 +162,40 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
|
|
|
124
162
|
|
|
125
163
|
expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
|
|
126
164
|
expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
|
|
127
|
-
expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(
|
|
165
|
+
expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(webusb.closeConnectionAfterProbe).toHaveBeenCalledTimes(1);
|
|
128
167
|
expect(webusb.deviceProtocol.has(path)).toBe(false);
|
|
129
168
|
});
|
|
130
169
|
|
|
170
|
+
test('closes the reopened device when acquire exhausts the expected Protocol V2 probe', async () => {
|
|
171
|
+
const webusb = new WebUsbTransport() as any;
|
|
172
|
+
const path = 'pro2-webusb';
|
|
173
|
+
const device = {
|
|
174
|
+
opened: false,
|
|
175
|
+
releaseInterface: jest.fn().mockResolvedValue(undefined),
|
|
176
|
+
close: jest.fn().mockImplementation(() => {
|
|
177
|
+
device.opened = false;
|
|
178
|
+
return Promise.resolve();
|
|
179
|
+
}),
|
|
180
|
+
};
|
|
181
|
+
webusb.deviceList = [{ path, device }];
|
|
182
|
+
webusb.Log = { debug: jest.fn() };
|
|
183
|
+
webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
|
|
184
|
+
webusb.connect = jest.fn().mockImplementation(() => {
|
|
185
|
+
device.opened = true;
|
|
186
|
+
return Promise.resolve();
|
|
187
|
+
});
|
|
188
|
+
webusb.detectProtocol = jest.fn().mockRejectedValue(new Error('terminal probe failure'));
|
|
189
|
+
|
|
190
|
+
await expect(webusb.acquire({ path, expectedProtocol: 'V2' })).rejects.toThrow(
|
|
191
|
+
'terminal probe failure'
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(device.releaseInterface).toHaveBeenCalledTimes(1);
|
|
195
|
+
expect(device.close).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(device.opened).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
131
199
|
test('invalidates and resets the cached connection before another call can start', async () => {
|
|
132
200
|
const webusb = new WebUsbTransport() as any;
|
|
133
201
|
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
|
@@ -41,14 +41,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
41
41
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
|
|
45
|
-
function shouldSuppressHighVolumeCallLog(name) {
|
|
46
|
-
return HIGH_VOLUME_CALLS.has(name);
|
|
47
|
-
}
|
|
48
|
-
function createTransportCallLog(name, protocol) {
|
|
49
|
-
return { name, protocol };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
const { parseConfigure: parseConfigure$1, check: check$1, ProtocolV1: ProtocolV1$1 } = transport__default["default"];
|
|
53
45
|
const CONFIGURATION_ID = 1;
|
|
54
46
|
const INTERFACE_ID = 0;
|
|
@@ -59,7 +51,8 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
|
59
51
|
const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
60
52
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
61
53
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
62
|
-
const
|
|
54
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
63
56
|
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
64
57
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
65
58
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
@@ -155,7 +148,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
155
148
|
});
|
|
156
149
|
}
|
|
157
150
|
acquire(input) {
|
|
158
|
-
var _a, _b, _c;
|
|
151
|
+
var _a, _b, _c, _d;
|
|
159
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
160
153
|
if (!input.path)
|
|
161
154
|
return;
|
|
@@ -166,7 +159,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
166
159
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
167
160
|
const protocolHint = input.expectedProtocol
|
|
168
161
|
? undefined
|
|
169
|
-
: (_c = this.deviceProtocolHints.get(input.path)) !== null &&
|
|
162
|
+
: (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
|
|
170
163
|
if (protocolHint) {
|
|
171
164
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
172
165
|
}
|
|
@@ -175,6 +168,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
175
168
|
}
|
|
176
169
|
catch (e) {
|
|
177
170
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
171
|
+
yield this.closeOpenDevice(input.path);
|
|
178
172
|
throw e;
|
|
179
173
|
}
|
|
180
174
|
});
|
|
@@ -186,7 +180,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
186
180
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
187
181
|
}
|
|
188
182
|
createProtocolDetectionError() {
|
|
189
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
183
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
190
184
|
}
|
|
191
185
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
192
186
|
var _a;
|
|
@@ -196,7 +190,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
196
190
|
this.deviceProtocol.set(path, 'V1');
|
|
197
191
|
return 'V1';
|
|
198
192
|
}
|
|
199
|
-
yield this.
|
|
193
|
+
yield this.closeConnectionAfterProbe(path);
|
|
200
194
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
201
195
|
}
|
|
202
196
|
if (expectedProtocol === 'V2') {
|
|
@@ -205,22 +199,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
205
199
|
this.deviceProtocol.set(path, 'V2');
|
|
206
200
|
return 'V2';
|
|
207
201
|
}
|
|
208
|
-
yield this.resetConnectionAfterProbe(path);
|
|
209
202
|
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
203
|
+
yield this.resetConnectionAfterProbe(path);
|
|
210
204
|
(_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
205
|
}
|
|
206
|
+
else {
|
|
207
|
+
yield this.closeConnectionAfterProbe(path);
|
|
208
|
+
}
|
|
212
209
|
}
|
|
213
210
|
this.deviceProtocol.delete(path);
|
|
214
211
|
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
215
212
|
}
|
|
216
213
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
217
|
-
for (const protocol of probeOrder) {
|
|
214
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
218
215
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
219
216
|
if (detected) {
|
|
220
217
|
this.deviceProtocol.set(path, protocol);
|
|
221
218
|
return protocol;
|
|
222
219
|
}
|
|
223
|
-
|
|
220
|
+
if (index < probeOrder.length - 1) {
|
|
221
|
+
yield this.resetConnectionAfterProbe(path);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
yield this.closeConnectionAfterProbe(path);
|
|
225
|
+
}
|
|
224
226
|
}
|
|
225
227
|
this.deviceProtocol.delete(path);
|
|
226
228
|
throw this.createProtocolDetectionError();
|
|
@@ -509,27 +511,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
509
511
|
return this.getTransferInData(result);
|
|
510
512
|
});
|
|
511
513
|
}
|
|
512
|
-
|
|
513
|
-
var _a;
|
|
514
|
+
closeConnectionAfterProbe(path) {
|
|
514
515
|
return __awaiter(this, void 0, void 0, function* () {
|
|
515
516
|
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
|
-
}
|
|
517
|
+
yield this.closeOpenDevice(path);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
resetConnectionAfterProbe(path) {
|
|
521
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
522
|
+
yield this.closeConnectionAfterProbe(path);
|
|
533
523
|
yield this.getConnectedDevices();
|
|
534
524
|
yield this.connect(path, false);
|
|
535
525
|
});
|
|
@@ -577,7 +567,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
577
567
|
return false;
|
|
578
568
|
}
|
|
579
569
|
try {
|
|
580
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
570
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
571
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
572
|
+
});
|
|
581
573
|
return true;
|
|
582
574
|
}
|
|
583
575
|
catch (_error) {
|
|
@@ -592,7 +584,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
592
584
|
}
|
|
593
585
|
return transport.probeProtocolV2({
|
|
594
586
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
595
|
-
timeoutMs:
|
|
587
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
596
588
|
logger: this.Log,
|
|
597
589
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
598
590
|
});
|
|
@@ -611,8 +603,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
611
603
|
if (!protocol) {
|
|
612
604
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
613
605
|
}
|
|
614
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
615
|
-
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
606
|
+
if (!transport.shouldSuppressHighVolumeCallLog(name)) {
|
|
607
|
+
this.Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
|
|
616
608
|
}
|
|
617
609
|
if (protocol === 'V2') {
|
|
618
610
|
return this.callProtocolV2(path, name, data, options);
|
|
@@ -747,6 +739,7 @@ class ElectronBleTransport {
|
|
|
747
739
|
this.name = 'ElectronBleTransport';
|
|
748
740
|
this.configured = false;
|
|
749
741
|
this.runPromise = null;
|
|
742
|
+
this.runPromiseDeviceId = null;
|
|
750
743
|
this.connectedDevices = new Set();
|
|
751
744
|
this.deviceProtocol = new Map();
|
|
752
745
|
this.deviceProtocolHints = new Map();
|
|
@@ -819,6 +812,11 @@ class ElectronBleTransport {
|
|
|
819
812
|
this.v2Assemblers.delete(deviceId);
|
|
820
813
|
this.resetProtocolV2Frames(deviceId);
|
|
821
814
|
this.notificationTokens.delete(deviceId);
|
|
815
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
816
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
|
|
817
|
+
this.runPromise = null;
|
|
818
|
+
this.runPromiseDeviceId = null;
|
|
819
|
+
}
|
|
822
820
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
823
821
|
if (notifyCleanup) {
|
|
824
822
|
notifyCleanup();
|
|
@@ -887,7 +885,7 @@ class ElectronBleTransport {
|
|
|
887
885
|
});
|
|
888
886
|
}
|
|
889
887
|
acquire(input) {
|
|
890
|
-
var _a, _b, _c, _d, _e;
|
|
888
|
+
var _a, _b, _c, _d, _e, _f;
|
|
891
889
|
return __awaiter(this, void 0, void 0, function* () {
|
|
892
890
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
893
891
|
if (!uuid) {
|
|
@@ -899,8 +897,8 @@ class ElectronBleTransport {
|
|
|
899
897
|
if (forceCleanRunPromise && this.runPromise) {
|
|
900
898
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
901
899
|
this.runPromise.reject(error);
|
|
902
|
-
this.rejectAllProtocolV2Frames(error);
|
|
903
900
|
this.runPromise = null;
|
|
901
|
+
this.runPromiseDeviceId = null;
|
|
904
902
|
}
|
|
905
903
|
try {
|
|
906
904
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -912,7 +910,7 @@ class ElectronBleTransport {
|
|
|
912
910
|
}
|
|
913
911
|
const protocolHint = expectedProtocol
|
|
914
912
|
? undefined
|
|
915
|
-
: (_b = this.deviceProtocolHints.get(uuid)) !== null &&
|
|
913
|
+
: (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
|
|
916
914
|
if (protocolHint) {
|
|
917
915
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
918
916
|
}
|
|
@@ -928,6 +926,7 @@ class ElectronBleTransport {
|
|
|
928
926
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
929
927
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
930
928
|
this.notificationCleanups.set(uuid, cleanup);
|
|
929
|
+
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
931
930
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
932
931
|
var _a;
|
|
933
932
|
if (disconnectedDevice.id === uuid) {
|
|
@@ -940,19 +939,18 @@ class ElectronBleTransport {
|
|
|
940
939
|
}
|
|
941
940
|
});
|
|
942
941
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
943
|
-
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
944
942
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
945
943
|
}
|
|
946
944
|
catch (error) {
|
|
947
|
-
(
|
|
945
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
|
|
948
946
|
try {
|
|
949
|
-
if (((
|
|
947
|
+
if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
950
948
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
951
949
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
952
950
|
}
|
|
953
951
|
}
|
|
954
952
|
catch (cleanupError) {
|
|
955
|
-
(
|
|
953
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
956
954
|
}
|
|
957
955
|
this.cleanupDeviceState(uuid);
|
|
958
956
|
throw error;
|
|
@@ -994,7 +992,7 @@ class ElectronBleTransport {
|
|
|
994
992
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
995
993
|
}
|
|
996
994
|
createProtocolDetectionError() {
|
|
997
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
995
|
+
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
996
|
}
|
|
999
997
|
clearProbeProtocol(uuid, protocol) {
|
|
1000
998
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1052,16 +1050,17 @@ class ElectronBleTransport {
|
|
|
1052
1050
|
});
|
|
1053
1051
|
}
|
|
1054
1052
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1055
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1053
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1056
1054
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1057
1055
|
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1058
1056
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1059
1057
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1060
1058
|
this.resetProtocolV2Frames(uuid);
|
|
1061
|
-
if (this.runPromise) {
|
|
1059
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1062
1060
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1063
1061
|
this.runPromise.reject(error);
|
|
1064
1062
|
this.runPromise = null;
|
|
1063
|
+
this.runPromiseDeviceId = null;
|
|
1065
1064
|
}
|
|
1066
1065
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1067
1066
|
if (notifyCleanup) {
|
|
@@ -1076,10 +1075,19 @@ class ElectronBleTransport {
|
|
|
1076
1075
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1077
1076
|
}
|
|
1078
1077
|
try {
|
|
1079
|
-
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.
|
|
1078
|
+
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
|
|
1080
1079
|
}
|
|
1081
1080
|
catch (error) {
|
|
1082
|
-
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE]
|
|
1081
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
1082
|
+
}
|
|
1083
|
+
this.connectedDevices.delete(uuid);
|
|
1084
|
+
try {
|
|
1085
|
+
yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
|
|
1086
|
+
this.connectedDevices.add(uuid);
|
|
1087
|
+
yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
(_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
1083
1091
|
throw error;
|
|
1084
1092
|
}
|
|
1085
1093
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1094,12 +1102,12 @@ class ElectronBleTransport {
|
|
|
1094
1102
|
}
|
|
1095
1103
|
try {
|
|
1096
1104
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1097
|
-
yield this.callProtocolV1(uuid, '
|
|
1105
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1098
1106
|
return true;
|
|
1099
1107
|
}
|
|
1100
1108
|
catch (error) {
|
|
1101
1109
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1102
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1110
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1103
1111
|
return false;
|
|
1104
1112
|
}
|
|
1105
1113
|
});
|
|
@@ -1160,7 +1168,7 @@ class ElectronBleTransport {
|
|
|
1160
1168
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1161
1169
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
1162
1170
|
}
|
|
1163
|
-
else if (this.runPromise) {
|
|
1171
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1164
1172
|
this.runPromise.reject(error);
|
|
1165
1173
|
}
|
|
1166
1174
|
return;
|
|
@@ -1212,16 +1220,8 @@ class ElectronBleTransport {
|
|
|
1212
1220
|
}
|
|
1213
1221
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1214
1222
|
}
|
|
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
1223
|
resetProtocolV2Frames(uuid) {
|
|
1223
|
-
this.
|
|
1224
|
-
this.v2FramePromises.delete(uuid);
|
|
1224
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1225
1225
|
}
|
|
1226
1226
|
rejectProtocolV2Frames(uuid, error) {
|
|
1227
1227
|
this.v2FrameQueues.delete(uuid);
|
|
@@ -1254,12 +1254,15 @@ class ElectronBleTransport {
|
|
|
1254
1254
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1255
1255
|
if (result.error) {
|
|
1256
1256
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1257
|
-
if (this.runPromise) {
|
|
1257
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1258
1258
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1259
1259
|
}
|
|
1260
1260
|
return;
|
|
1261
1261
|
}
|
|
1262
|
-
if (result.isComplete &&
|
|
1262
|
+
if (result.isComplete &&
|
|
1263
|
+
result.completePacket &&
|
|
1264
|
+
this.runPromise &&
|
|
1265
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1263
1266
|
this.runPromise.resolve(result.completePacket);
|
|
1264
1267
|
}
|
|
1265
1268
|
}
|
|
@@ -1276,8 +1279,8 @@ class ElectronBleTransport {
|
|
|
1276
1279
|
if (!protocol) {
|
|
1277
1280
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1278
1281
|
}
|
|
1279
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
1280
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
|
|
1282
|
+
if (!transport.shouldSuppressHighVolumeCallLog(name)) {
|
|
1283
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', transport.createTransportCallLog(name, protocol, data));
|
|
1281
1284
|
}
|
|
1282
1285
|
if (protocol === 'V2') {
|
|
1283
1286
|
return this.callProtocolV2(uuid, name, data, options);
|
|
@@ -1298,6 +1301,7 @@ class ElectronBleTransport {
|
|
|
1298
1301
|
const runPromise = hdShared.createDeferred();
|
|
1299
1302
|
runPromise.promise.catch(() => undefined);
|
|
1300
1303
|
this.runPromise = runPromise;
|
|
1304
|
+
this.runPromiseDeviceId = uuid;
|
|
1301
1305
|
const messages = this._messages;
|
|
1302
1306
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1303
1307
|
let timeout;
|
|
@@ -1336,6 +1340,17 @@ class ElectronBleTransport {
|
|
|
1336
1340
|
}
|
|
1337
1341
|
catch (e) {
|
|
1338
1342
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1343
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1344
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1345
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1346
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1347
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1348
|
+
this.notificationCleanups.delete(uuid);
|
|
1349
|
+
this.notificationTokens.delete(uuid);
|
|
1350
|
+
if (!isProbeTimeout) {
|
|
1351
|
+
yield this.releaseNative(uuid);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1339
1354
|
throw e;
|
|
1340
1355
|
}
|
|
1341
1356
|
finally {
|
|
@@ -1343,6 +1358,7 @@ class ElectronBleTransport {
|
|
|
1343
1358
|
clearTimeout(timeout);
|
|
1344
1359
|
if (this.runPromise === runPromise) {
|
|
1345
1360
|
this.runPromise = null;
|
|
1361
|
+
this.runPromiseDeviceId = null;
|
|
1346
1362
|
}
|
|
1347
1363
|
}
|
|
1348
1364
|
});
|
package/dist/transportLog.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function shouldSuppressHighVolumeCallLog(name: string): boolean;
|
|
3
|
-
export declare function createTransportCallLog(name: string, protocol: ProtocolType): {
|
|
4
|
-
name: string;
|
|
5
|
-
protocol: ProtocolType;
|
|
6
|
-
};
|
|
1
|
+
export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
|
|
7
2
|
//# sourceMappingURL=transportLog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
|
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":";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;AA0BhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAaD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC7E,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,eAAe,CAA2C;IAElE,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;;IAa3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAsB7B,kBAAkB;IAmBlB,SAAS;IAQT,mBAAmB;IAiCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA8BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAItB,cAAc;IAmEtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;YAgCpC,eAAe;YAkBf,iBAAiB;IAezB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAiCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,cAAc;YAWd,yBAAyB;YAKzB,yBAAyB;YAMzB,uBAAuB;YA0CvB,eAAe;YAoBf,eAAe;IAgBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA8BlB,cAAc;YAkCd,cAAc;IAYtB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAQ1B,SAAS,CAAC,uBAAuB,IAAI,iBAAiB;IAUtD,SAAS,CAAC,sBAAsB;cAIhB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;cAIA,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC;cASN,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,SAAS,CAAC,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKrE,SAAS,CAAC,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK;IAWjF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.35",
|
|
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.35",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.35"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.35",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "638bd33030686c4a3321e4feb4924c3f9f5c81a8"
|
|
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
|
}
|
|
@@ -689,7 +706,7 @@ export default class ElectronBleTransport {
|
|
|
689
706
|
);
|
|
690
707
|
}
|
|
691
708
|
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
692
|
-
this.Log?.debug('transport call', createTransportCallLog(name, protocol));
|
|
709
|
+
this.Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
693
710
|
}
|
|
694
711
|
|
|
695
712
|
if (protocol === 'V2') {
|
|
@@ -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/transportLog.ts
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
|
|
4
|
-
|
|
5
|
-
export function shouldSuppressHighVolumeCallLog(name: string) {
|
|
6
|
-
return HIGH_VOLUME_CALLS.has(name);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function createTransportCallLog(name: string, protocol: ProtocolType) {
|
|
10
|
-
return { name, protocol };
|
|
11
|
-
}
|
|
1
|
+
export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
|
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
|
}
|
|
@@ -265,10 +272,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
private createProtocolDetectionError() {
|
|
268
|
-
return ERRORS.TypedError(
|
|
269
|
-
HardwareErrorCode.RuntimeError,
|
|
270
|
-
'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
|
|
271
|
-
);
|
|
275
|
+
return ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
private async detectProtocol(
|
|
@@ -281,7 +285,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
281
285
|
this.deviceProtocol.set(path, 'V1');
|
|
282
286
|
return 'V1';
|
|
283
287
|
}
|
|
284
|
-
await this.
|
|
288
|
+
await this.closeConnectionAfterProbe(path);
|
|
285
289
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
286
290
|
}
|
|
287
291
|
|
|
@@ -291,13 +295,15 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
291
295
|
this.deviceProtocol.set(path, 'V2');
|
|
292
296
|
return 'V2';
|
|
293
297
|
}
|
|
294
|
-
await this.resetConnectionAfterProbe(path);
|
|
295
298
|
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
299
|
+
await this.resetConnectionAfterProbe(path);
|
|
296
300
|
this.Log?.debug(
|
|
297
301
|
`[WebUsbTransport] Protocol V2 probe timed out, retrying ${
|
|
298
302
|
attempt + 1
|
|
299
303
|
}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
|
|
300
304
|
);
|
|
305
|
+
} else {
|
|
306
|
+
await this.closeConnectionAfterProbe(path);
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
this.deviceProtocol.delete(path);
|
|
@@ -312,17 +318,21 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
312
318
|
const probeOrder: ProtocolType[] =
|
|
313
319
|
protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
314
320
|
|
|
315
|
-
for (const protocol of probeOrder) {
|
|
321
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
316
322
|
const detected =
|
|
317
323
|
protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
|
|
318
324
|
if (detected) {
|
|
319
325
|
this.deviceProtocol.set(path, protocol);
|
|
320
326
|
return protocol;
|
|
321
327
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
328
|
+
if (index < probeOrder.length - 1) {
|
|
329
|
+
// A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
|
|
330
|
+
// reopening the device guarantees the next protocol probe cannot consume a
|
|
331
|
+
// late response from the previous protocol generation.
|
|
332
|
+
await this.resetConnectionAfterProbe(path);
|
|
333
|
+
} else {
|
|
334
|
+
await this.closeConnectionAfterProbe(path);
|
|
335
|
+
}
|
|
326
336
|
}
|
|
327
337
|
|
|
328
338
|
this.deviceProtocol.delete(path);
|
|
@@ -645,25 +655,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
645
655
|
return this.getTransferInData(result);
|
|
646
656
|
}
|
|
647
657
|
|
|
648
|
-
private async
|
|
658
|
+
private async closeConnectionAfterProbe(path: string) {
|
|
649
659
|
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
660
|
+
await this.closeOpenDevice(path);
|
|
661
|
+
}
|
|
650
662
|
|
|
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
|
-
|
|
663
|
+
private async resetConnectionAfterProbe(path: string) {
|
|
664
|
+
await this.closeConnectionAfterProbe(path);
|
|
667
665
|
await this.getConnectedDevices();
|
|
668
666
|
await this.connect(path, false);
|
|
669
667
|
}
|
|
@@ -716,7 +714,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
716
714
|
}
|
|
717
715
|
|
|
718
716
|
try {
|
|
719
|
-
await this.callProtocolV1(
|
|
717
|
+
await this.callProtocolV1(
|
|
718
|
+
path,
|
|
719
|
+
'Initialize',
|
|
720
|
+
{},
|
|
721
|
+
{
|
|
722
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
723
|
+
}
|
|
724
|
+
);
|
|
720
725
|
return true;
|
|
721
726
|
} catch (_error) {
|
|
722
727
|
return false;
|
|
@@ -730,7 +735,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
730
735
|
|
|
731
736
|
return probeProtocolV2Helper({
|
|
732
737
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
733
|
-
timeoutMs:
|
|
738
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
734
739
|
logger: this.Log,
|
|
735
740
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
736
741
|
});
|
|
@@ -763,7 +768,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
763
768
|
}
|
|
764
769
|
|
|
765
770
|
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
766
|
-
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
771
|
+
this.Log.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
767
772
|
}
|
|
768
773
|
|
|
769
774
|
if (protocol === 'V2') {
|