@onekeyfe/hd-transport-web-device 1.2.0-alpha.7 → 1.2.0-alpha.70
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 +396 -12
- package/__tests__/webusb-protocol-v2-timeout.test.ts +385 -0
- package/dist/electron-ble-transport.d.ts +8 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +101 -14
- package/dist/index.js +320 -321
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/webusb.d.ts +15 -10
- package/dist/webusb.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +6 -5
- package/src/electron-ble-transport.ts +218 -175
- package/src/transportLog.ts +1 -0
- package/src/webusb.ts +184 -217
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import transport, {
|
|
2
|
-
LogBlockCommand,
|
|
3
2
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
3
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
4
4
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
5
5
|
ProtocolV2FrameAssembler,
|
|
6
|
-
|
|
6
|
+
ProtocolV2LinkManager,
|
|
7
|
+
TRANSPORT_EVENT,
|
|
7
8
|
bytesToHex,
|
|
8
9
|
hexToBytes,
|
|
9
10
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
11
|
+
writeProtocolV2BleFrame,
|
|
10
12
|
} from '@onekeyfe/hd-transport';
|
|
11
13
|
import {
|
|
12
14
|
ERRORS,
|
|
@@ -14,25 +16,20 @@ import {
|
|
|
14
16
|
HardwareErrorCodeMessage,
|
|
15
17
|
createDeferred,
|
|
16
18
|
isHeaderChunk,
|
|
17
|
-
wait,
|
|
18
19
|
} from '@onekeyfe/hd-shared';
|
|
19
20
|
|
|
21
|
+
import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
|
|
22
|
+
|
|
20
23
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
21
24
|
import type { DesktopAPI } from '@onekeyfe/hd-transport-electron';
|
|
22
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
OneKeyDeviceInfo,
|
|
27
|
+
ProtocolType,
|
|
28
|
+
ProtocolV2CallContext,
|
|
29
|
+
TransportCallOptions,
|
|
30
|
+
} from '@onekeyfe/hd-transport';
|
|
23
31
|
import type EventEmitter from 'events';
|
|
24
32
|
|
|
25
|
-
const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
|
|
26
|
-
|
|
27
|
-
function shouldSuppressHighVolumeCallLog(name: string) {
|
|
28
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
29
|
-
return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isLogBlockCommand(name: string) {
|
|
33
|
-
return (LogBlockCommand as Set<string> | undefined)?.has?.(name) ?? false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
33
|
const { parseConfigure, ProtocolV1, check } = transport;
|
|
37
34
|
|
|
38
35
|
declare global {
|
|
@@ -45,6 +42,7 @@ export type BleAcquireInput = {
|
|
|
45
42
|
uuid: string;
|
|
46
43
|
forceCleanRunPromise?: boolean;
|
|
47
44
|
expectedProtocol?: ProtocolType;
|
|
45
|
+
protocolHint?: ProtocolType;
|
|
48
46
|
};
|
|
49
47
|
|
|
50
48
|
interface PacketProcessResult {
|
|
@@ -72,7 +70,6 @@ const toBleDescriptor = (
|
|
|
72
70
|
|
|
73
71
|
const BLE_PACKET_SIZE = 192;
|
|
74
72
|
const BLE_WRITE_DELAY_MS = 5;
|
|
75
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
76
73
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
77
74
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
78
75
|
|
|
@@ -80,19 +77,23 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
|
80
77
|
* Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
|
|
81
78
|
*
|
|
82
79
|
* Protocol V1 devices continue using chunked packets. Protocol V2 is detected
|
|
83
|
-
* after a Protocol V1
|
|
80
|
+
* after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
|
|
84
81
|
*/
|
|
85
82
|
export default class ElectronBleTransport {
|
|
86
83
|
private _messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
87
84
|
|
|
88
85
|
private _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
89
86
|
|
|
87
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
88
|
+
|
|
90
89
|
name = 'ElectronBleTransport';
|
|
91
90
|
|
|
92
91
|
configured = false;
|
|
93
92
|
|
|
94
93
|
runPromise: Deferred<Uint8Array | string> | null = null;
|
|
95
94
|
|
|
95
|
+
private runPromiseDeviceId: string | null = null;
|
|
96
|
+
|
|
96
97
|
Log?: any;
|
|
97
98
|
|
|
98
99
|
emitter?: EventEmitter;
|
|
@@ -111,9 +112,26 @@ export default class ElectronBleTransport {
|
|
|
111
112
|
|
|
112
113
|
private v2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
113
114
|
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
116
|
+
getSchemas: () => {
|
|
117
|
+
if (!this._messages || !this._messagesV2) {
|
|
118
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
protocolV1: this._messages,
|
|
122
|
+
protocolV2: this._messagesV2,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
classifyError: () => 'link-fatal',
|
|
126
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
127
|
+
this.v2Assemblers.get(uuid)?.reset();
|
|
128
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
129
|
+
this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
130
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
131
|
+
await this.releaseNative(uuid);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
117
135
|
|
|
118
136
|
private notificationCleanups: Map<string, () => void> = new Map();
|
|
119
137
|
|
|
@@ -155,6 +173,9 @@ export default class ElectronBleTransport {
|
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
private cleanupDeviceState(deviceId: string): void {
|
|
176
|
+
this.protocolV2Links
|
|
177
|
+
.invalidateLink(deviceId, 'Electron BLE device state cleaned')
|
|
178
|
+
.catch(error => this.Log?.debug('[Electron BLE] link cleanup failed:', error));
|
|
158
179
|
this.connectedDevices.delete(deviceId);
|
|
159
180
|
this.deviceProtocol.delete(deviceId);
|
|
160
181
|
// Keep deviceProtocolHints — it's inferred from device name (e.g. "Pro 2" → V2)
|
|
@@ -162,10 +183,12 @@ export default class ElectronBleTransport {
|
|
|
162
183
|
this.v1Buffers.delete(deviceId);
|
|
163
184
|
this.v2Assemblers.delete(deviceId);
|
|
164
185
|
this.resetProtocolV2Frames(deviceId);
|
|
165
|
-
if (this.activeProtocolV2Call?.uuid === deviceId) {
|
|
166
|
-
this.activeProtocolV2Call = null;
|
|
167
|
-
}
|
|
168
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
|
+
}
|
|
169
192
|
|
|
170
193
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
171
194
|
if (notifyCleanup) {
|
|
@@ -200,8 +223,19 @@ export default class ElectronBleTransport {
|
|
|
200
223
|
}
|
|
201
224
|
|
|
202
225
|
configureProtocolV2(signedData: any) {
|
|
226
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
227
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
203
232
|
this._messagesV2 = parseConfigure(signedData);
|
|
204
|
-
this.
|
|
233
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
234
|
+
if (isReconfiguration) {
|
|
235
|
+
this.protocolV2Links
|
|
236
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
237
|
+
.catch(error => this.Log?.debug('[Electron BLE] schema link cleanup failed:', error));
|
|
238
|
+
}
|
|
205
239
|
}
|
|
206
240
|
|
|
207
241
|
async listen() {
|
|
@@ -236,12 +270,15 @@ export default class ElectronBleTransport {
|
|
|
236
270
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
237
271
|
}
|
|
238
272
|
|
|
273
|
+
if (this.connectedDevices.has(uuid)) {
|
|
274
|
+
await this.release(uuid);
|
|
275
|
+
}
|
|
276
|
+
|
|
239
277
|
if (forceCleanRunPromise && this.runPromise) {
|
|
240
278
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
241
279
|
this.runPromise.reject(error);
|
|
242
|
-
this.rejectAllProtocolV2Frames(error);
|
|
243
280
|
this.runPromise = null;
|
|
244
|
-
this.
|
|
281
|
+
this.runPromiseDeviceId = null;
|
|
245
282
|
}
|
|
246
283
|
|
|
247
284
|
try {
|
|
@@ -255,7 +292,9 @@ export default class ElectronBleTransport {
|
|
|
255
292
|
}
|
|
256
293
|
const protocolHint = expectedProtocol
|
|
257
294
|
? undefined
|
|
258
|
-
:
|
|
295
|
+
: input.protocolHint ??
|
|
296
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
297
|
+
inferProtocolHintFromDeviceName(device.name);
|
|
259
298
|
if (protocolHint) {
|
|
260
299
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
261
300
|
}
|
|
@@ -268,18 +307,24 @@ export default class ElectronBleTransport {
|
|
|
268
307
|
}
|
|
269
308
|
|
|
270
309
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
271
|
-
this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
310
|
+
this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
272
311
|
|
|
273
312
|
await window.desktopApi.nobleBle.subscribe(uuid);
|
|
274
313
|
|
|
275
314
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
276
315
|
this.notificationCleanups.set(uuid, cleanup);
|
|
316
|
+
const connectionToken = this.notificationTokens.get(uuid);
|
|
317
|
+
|
|
318
|
+
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
277
319
|
|
|
278
320
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
|
|
279
321
|
(disconnectedDevice: any) => {
|
|
280
|
-
if (
|
|
322
|
+
if (
|
|
323
|
+
disconnectedDevice.id === uuid &&
|
|
324
|
+
this.notificationTokens.get(uuid) === connectionToken
|
|
325
|
+
) {
|
|
281
326
|
this.cleanupDeviceState(uuid);
|
|
282
|
-
this.emitter?.emit(
|
|
327
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
283
328
|
name: disconnectedDevice.name,
|
|
284
329
|
id: disconnectedDevice.id,
|
|
285
330
|
connectId: disconnectedDevice.id,
|
|
@@ -289,14 +334,6 @@ export default class ElectronBleTransport {
|
|
|
289
334
|
);
|
|
290
335
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
291
336
|
|
|
292
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
293
|
-
|
|
294
|
-
this.emitter?.emit('device-connect', {
|
|
295
|
-
name: device.name,
|
|
296
|
-
id: device.id,
|
|
297
|
-
connectId: device.id,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
337
|
return {
|
|
301
338
|
...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
|
|
302
339
|
uuid,
|
|
@@ -317,6 +354,16 @@ export default class ElectronBleTransport {
|
|
|
317
354
|
}
|
|
318
355
|
|
|
319
356
|
async release(id: string) {
|
|
357
|
+
try {
|
|
358
|
+
await this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
359
|
+
await this.releaseNative(id);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
this.Log?.error('[Electron BLE] release failed:', error);
|
|
362
|
+
this.cleanupDeviceState(id);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private async releaseNative(id: string) {
|
|
320
367
|
try {
|
|
321
368
|
if (this.connectedDevices.has(id)) {
|
|
322
369
|
if (window.desktopApi?.nobleBle) {
|
|
@@ -341,7 +388,7 @@ export default class ElectronBleTransport {
|
|
|
341
388
|
private createProtocolDetectionError() {
|
|
342
389
|
return ERRORS.TypedError(
|
|
343
390
|
HardwareErrorCode.BleTimeoutError,
|
|
344
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
391
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
345
392
|
);
|
|
346
393
|
}
|
|
347
394
|
|
|
@@ -366,23 +413,23 @@ export default class ElectronBleTransport {
|
|
|
366
413
|
}
|
|
367
414
|
|
|
368
415
|
if (expectedProtocol === 'V2') {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
416
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
417
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
418
|
+
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
419
|
+
return 'V2';
|
|
420
|
+
}
|
|
421
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
374
422
|
}
|
|
375
423
|
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// 不能作为最终结论。
|
|
424
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
425
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
379
426
|
const probeOrder: ProtocolType[] =
|
|
380
427
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
381
428
|
|
|
382
429
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
383
430
|
const protocol = probeOrder[i];
|
|
384
431
|
if (i > 0) {
|
|
385
|
-
//
|
|
432
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
386
433
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
387
434
|
}
|
|
388
435
|
const detected =
|
|
@@ -415,16 +462,18 @@ export default class ElectronBleTransport {
|
|
|
415
462
|
}
|
|
416
463
|
|
|
417
464
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
465
|
+
await this.protocolV2Links.invalidateLink(
|
|
466
|
+
uuid,
|
|
467
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
468
|
+
);
|
|
418
469
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
419
470
|
this.v2Assemblers.get(uuid)?.reset();
|
|
420
471
|
this.resetProtocolV2Frames(uuid);
|
|
421
|
-
if (this.
|
|
422
|
-
this.activeProtocolV2Call = null;
|
|
423
|
-
}
|
|
424
|
-
if (this.runPromise) {
|
|
472
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
425
473
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
426
474
|
this.runPromise.reject(error);
|
|
427
475
|
this.runPromise = null;
|
|
476
|
+
this.runPromiseDeviceId = null;
|
|
428
477
|
}
|
|
429
478
|
|
|
430
479
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
@@ -440,9 +489,17 @@ export default class ElectronBleTransport {
|
|
|
440
489
|
this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
441
490
|
}
|
|
442
491
|
try {
|
|
492
|
+
await window.desktopApi?.nobleBle?.disconnect(uuid);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
this.Log?.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
495
|
+
}
|
|
496
|
+
this.connectedDevices.delete(uuid);
|
|
497
|
+
try {
|
|
498
|
+
await window.desktopApi?.nobleBle?.connect(uuid);
|
|
499
|
+
this.connectedDevices.add(uuid);
|
|
443
500
|
await window.desktopApi?.nobleBle?.subscribe(uuid);
|
|
444
501
|
} catch (error) {
|
|
445
|
-
this.Log?.debug(`[Electron BLE]
|
|
502
|
+
this.Log?.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
446
503
|
throw error;
|
|
447
504
|
}
|
|
448
505
|
|
|
@@ -457,11 +514,13 @@ export default class ElectronBleTransport {
|
|
|
457
514
|
|
|
458
515
|
try {
|
|
459
516
|
this.deviceProtocol.set(uuid, 'V1');
|
|
460
|
-
|
|
517
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
518
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
519
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
461
520
|
return true;
|
|
462
521
|
} catch (error) {
|
|
463
522
|
this.clearProbeProtocol(uuid, 'V1');
|
|
464
|
-
this.Log?.debug('[Electron BLE] Protocol V1
|
|
523
|
+
this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
465
524
|
return false;
|
|
466
525
|
}
|
|
467
526
|
}
|
|
@@ -489,28 +548,6 @@ export default class ElectronBleTransport {
|
|
|
489
548
|
return detected;
|
|
490
549
|
}
|
|
491
550
|
|
|
492
|
-
private async writeWithChunking(uuid: string, hexData: string): Promise<void> {
|
|
493
|
-
const totalBytes = hexData.length / 2;
|
|
494
|
-
|
|
495
|
-
if (totalBytes <= BLE_PACKET_SIZE) {
|
|
496
|
-
await wait(BLE_WRITE_DELAY_MS);
|
|
497
|
-
await this.writeOnce(uuid, hexData);
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
for (let offset = 0; offset < hexData.length; ) {
|
|
502
|
-
const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
|
|
503
|
-
const chunkHex = hexData.substring(offset, offset + chunkHexLen);
|
|
504
|
-
offset += chunkHexLen;
|
|
505
|
-
|
|
506
|
-
await this.writeOnce(uuid, chunkHex);
|
|
507
|
-
|
|
508
|
-
if (offset < hexData.length) {
|
|
509
|
-
await wait(BLE_WRITE_DELAY_MS);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
551
|
private async writeOnce(uuid: string, hexData: string): Promise<void> {
|
|
515
552
|
const nobleBle = window.desktopApi?.nobleBle;
|
|
516
553
|
if (!nobleBle) {
|
|
@@ -520,13 +557,33 @@ export default class ElectronBleTransport {
|
|
|
520
557
|
await nobleBle.write(uuid, hexData);
|
|
521
558
|
}
|
|
522
559
|
|
|
560
|
+
private writeProtocolV2Frame(
|
|
561
|
+
uuid: string,
|
|
562
|
+
frame: Uint8Array,
|
|
563
|
+
context: ProtocolV2CallContext,
|
|
564
|
+
assertCurrentGeneration: () => void
|
|
565
|
+
) {
|
|
566
|
+
return writeProtocolV2BleFrame({
|
|
567
|
+
frame,
|
|
568
|
+
packetCapacity: BLE_PACKET_SIZE,
|
|
569
|
+
assertActive: assertCurrentGeneration,
|
|
570
|
+
signal: context.signal,
|
|
571
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
572
|
+
initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
|
|
573
|
+
burstSize: 1,
|
|
574
|
+
burstPauseMs: BLE_WRITE_DELAY_MS,
|
|
575
|
+
writePacket: packet => this.writeOnce(uuid, bytesToHex(packet)),
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
523
579
|
private handleNotification(deviceId: string, hexData: string): void {
|
|
524
580
|
if (hexData === 'PAIRING_REJECTED') {
|
|
525
581
|
this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
|
|
526
|
-
|
|
527
|
-
|
|
582
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
|
|
583
|
+
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
584
|
+
this.rejectProtocolV2Frames(deviceId, error);
|
|
585
|
+
} else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
528
586
|
this.runPromise.reject(error);
|
|
529
|
-
this.rejectAllProtocolV2Frames(error);
|
|
530
587
|
}
|
|
531
588
|
return;
|
|
532
589
|
}
|
|
@@ -545,12 +602,6 @@ export default class ElectronBleTransport {
|
|
|
545
602
|
|
|
546
603
|
private handleProtocolV2Notification(deviceId: string, hexData: string): void {
|
|
547
604
|
try {
|
|
548
|
-
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
|
|
549
|
-
this.v2Assemblers.get(deviceId)?.reset();
|
|
550
|
-
this.resetProtocolV2Frames(deviceId);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
605
|
const bytes = hexToBytes(hexData);
|
|
555
606
|
if (bytes.length === 0) return;
|
|
556
607
|
|
|
@@ -562,11 +613,8 @@ export default class ElectronBleTransport {
|
|
|
562
613
|
}
|
|
563
614
|
} catch (error) {
|
|
564
615
|
this.Log?.error('[Electron BLE] Protocol V2 notification error:', error);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
this.runPromise.reject(notifyError);
|
|
568
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
569
|
-
}
|
|
616
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
617
|
+
this.rejectProtocolV2Frames(deviceId, notifyError);
|
|
570
618
|
}
|
|
571
619
|
}
|
|
572
620
|
|
|
@@ -589,21 +637,17 @@ export default class ElectronBleTransport {
|
|
|
589
637
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
590
638
|
}
|
|
591
639
|
|
|
592
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
593
|
-
this.v2FrameQueues.clear();
|
|
594
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
595
|
-
framePromise.reject(error);
|
|
596
|
-
}
|
|
597
|
-
this.v2FramePromises.clear();
|
|
598
|
-
}
|
|
599
|
-
|
|
600
640
|
private resetProtocolV2Frames(uuid: string) {
|
|
601
|
-
this.
|
|
602
|
-
this.v2FramePromises.delete(uuid);
|
|
641
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
603
642
|
}
|
|
604
643
|
|
|
605
|
-
private
|
|
606
|
-
|
|
644
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
645
|
+
this.v2FrameQueues.delete(uuid);
|
|
646
|
+
const framePromise = this.v2FramePromises.get(uuid);
|
|
647
|
+
if (framePromise) {
|
|
648
|
+
this.v2FramePromises.delete(uuid);
|
|
649
|
+
framePromise.reject(error);
|
|
650
|
+
}
|
|
607
651
|
}
|
|
608
652
|
|
|
609
653
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -628,13 +672,18 @@ export default class ElectronBleTransport {
|
|
|
628
672
|
|
|
629
673
|
if (result.error) {
|
|
630
674
|
this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
631
|
-
if (this.runPromise) {
|
|
675
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
632
676
|
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
|
|
633
677
|
}
|
|
634
678
|
return;
|
|
635
679
|
}
|
|
636
680
|
|
|
637
|
-
if (
|
|
681
|
+
if (
|
|
682
|
+
result.isComplete &&
|
|
683
|
+
result.completePacket &&
|
|
684
|
+
this.runPromise &&
|
|
685
|
+
this.runPromiseDeviceId === deviceId
|
|
686
|
+
) {
|
|
638
687
|
this.runPromise.resolve(result.completePacket);
|
|
639
688
|
}
|
|
640
689
|
}
|
|
@@ -660,12 +709,8 @@ export default class ElectronBleTransport {
|
|
|
660
709
|
`Device protocol has not been detected for ${uuid}`
|
|
661
710
|
);
|
|
662
711
|
}
|
|
663
|
-
if (shouldSuppressHighVolumeCallLog(name)) {
|
|
664
|
-
|
|
665
|
-
} else if (isLogBlockCommand(name)) {
|
|
666
|
-
this.Log?.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
|
|
667
|
-
} else {
|
|
668
|
-
this.Log?.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
|
|
712
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
713
|
+
this.Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
669
714
|
}
|
|
670
715
|
|
|
671
716
|
if (protocol === 'V2') {
|
|
@@ -692,6 +737,7 @@ export default class ElectronBleTransport {
|
|
|
692
737
|
const runPromise = createDeferred<Uint8Array | string>();
|
|
693
738
|
runPromise.promise.catch(() => undefined);
|
|
694
739
|
this.runPromise = runPromise;
|
|
740
|
+
this.runPromiseDeviceId = uuid;
|
|
695
741
|
const messages = this._messages;
|
|
696
742
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
697
743
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -736,11 +782,24 @@ export default class ElectronBleTransport {
|
|
|
736
782
|
return check.call(jsonData);
|
|
737
783
|
} catch (e) {
|
|
738
784
|
this.Log?.error('[Electron BLE] Protocol V1 call error:', e);
|
|
785
|
+
const isProbeTimeout =
|
|
786
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
787
|
+
if ((e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError) {
|
|
788
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
789
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
790
|
+
notifyCleanup?.();
|
|
791
|
+
this.notificationCleanups.delete(uuid);
|
|
792
|
+
this.notificationTokens.delete(uuid);
|
|
793
|
+
if (!isProbeTimeout) {
|
|
794
|
+
await this.releaseNative(uuid);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
739
797
|
throw e;
|
|
740
798
|
} finally {
|
|
741
799
|
if (timeout) clearTimeout(timeout);
|
|
742
800
|
if (this.runPromise === runPromise) {
|
|
743
801
|
this.runPromise = null;
|
|
802
|
+
this.runPromiseDeviceId = null;
|
|
744
803
|
}
|
|
745
804
|
}
|
|
746
805
|
}
|
|
@@ -755,77 +814,61 @@ export default class ElectronBleTransport {
|
|
|
755
814
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
756
815
|
}
|
|
757
816
|
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
817
|
+
const callOptions = options;
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
return await this.protocolV2Links.call(
|
|
821
|
+
uuid,
|
|
822
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
823
|
+
name,
|
|
824
|
+
data,
|
|
825
|
+
callOptions
|
|
826
|
+
);
|
|
827
|
+
} catch (e) {
|
|
828
|
+
this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
|
|
829
|
+
throw e;
|
|
768
830
|
}
|
|
831
|
+
}
|
|
769
832
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
this.resetProtocolV2Frames(uuid);
|
|
777
|
-
let completed = false;
|
|
778
|
-
const callOptions = {
|
|
779
|
-
...options,
|
|
780
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
833
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
834
|
+
const generation = this.notificationTokens.get(uuid) ?? 0;
|
|
835
|
+
const assertCurrentGeneration = () => {
|
|
836
|
+
if (this.notificationTokens.get(uuid) !== generation) {
|
|
837
|
+
throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
|
|
838
|
+
}
|
|
781
839
|
};
|
|
782
840
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
790
|
-
writeFrame: (frame: Uint8Array) => this.writeWithChunking(uuid, bytesToHex(frame)),
|
|
791
|
-
readFrame: async () => {
|
|
792
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
793
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
794
|
-
throw new Error('Response is not Uint8Array');
|
|
795
|
-
}
|
|
796
|
-
return rxFrame;
|
|
797
|
-
},
|
|
798
|
-
logger: this.Log,
|
|
799
|
-
logPrefix: 'ProtocolV2 BLE',
|
|
800
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
801
|
-
ERRORS.TypedError(
|
|
802
|
-
HardwareErrorCode.BleTimeoutError,
|
|
803
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
804
|
-
),
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
const result = await session.call(name, data, callOptions);
|
|
808
|
-
completed = true;
|
|
809
|
-
return result;
|
|
810
|
-
} catch (e) {
|
|
811
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
841
|
+
return {
|
|
842
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
843
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
844
|
+
generation,
|
|
845
|
+
prepareCall: () => {
|
|
846
|
+
assertCurrentGeneration();
|
|
812
847
|
this.v2Assemblers.get(uuid)?.reset();
|
|
813
848
|
this.resetProtocolV2Frames(uuid);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
849
|
+
},
|
|
850
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
851
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
852
|
+
readFrame: async () => {
|
|
853
|
+
assertCurrentGeneration();
|
|
854
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
855
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
856
|
+
throw new Error('Response is not Uint8Array');
|
|
821
857
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
this.
|
|
827
|
-
}
|
|
828
|
-
|
|
858
|
+
return rxFrame;
|
|
859
|
+
},
|
|
860
|
+
reset: (reason: string) => {
|
|
861
|
+
this.v2Assemblers.get(uuid)?.reset();
|
|
862
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
863
|
+
},
|
|
864
|
+
logger: this.Log,
|
|
865
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
866
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
867
|
+
ERRORS.TypedError(
|
|
868
|
+
HardwareErrorCode.BleTimeoutError,
|
|
869
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
870
|
+
),
|
|
871
|
+
};
|
|
829
872
|
}
|
|
830
873
|
|
|
831
874
|
private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
|