@onekeyfe/hd-transport-web-device 1.2.0-alpha.4 → 1.2.0-alpha.41
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 +349 -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 +317 -320
- 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 +213 -174
- 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,20 @@ 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);
|
|
277
316
|
|
|
317
|
+
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
318
|
+
|
|
278
319
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
|
|
279
320
|
(disconnectedDevice: any) => {
|
|
280
321
|
if (disconnectedDevice.id === uuid) {
|
|
281
322
|
this.cleanupDeviceState(uuid);
|
|
282
|
-
this.emitter?.emit(
|
|
323
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
283
324
|
name: disconnectedDevice.name,
|
|
284
325
|
id: disconnectedDevice.id,
|
|
285
326
|
connectId: disconnectedDevice.id,
|
|
@@ -289,14 +330,6 @@ export default class ElectronBleTransport {
|
|
|
289
330
|
);
|
|
290
331
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
291
332
|
|
|
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
333
|
return {
|
|
301
334
|
...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
|
|
302
335
|
uuid,
|
|
@@ -317,6 +350,16 @@ export default class ElectronBleTransport {
|
|
|
317
350
|
}
|
|
318
351
|
|
|
319
352
|
async release(id: string) {
|
|
353
|
+
try {
|
|
354
|
+
await this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
355
|
+
await this.releaseNative(id);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
this.Log?.error('[Electron BLE] release failed:', error);
|
|
358
|
+
this.cleanupDeviceState(id);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async releaseNative(id: string) {
|
|
320
363
|
try {
|
|
321
364
|
if (this.connectedDevices.has(id)) {
|
|
322
365
|
if (window.desktopApi?.nobleBle) {
|
|
@@ -341,7 +384,7 @@ export default class ElectronBleTransport {
|
|
|
341
384
|
private createProtocolDetectionError() {
|
|
342
385
|
return ERRORS.TypedError(
|
|
343
386
|
HardwareErrorCode.BleTimeoutError,
|
|
344
|
-
'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'
|
|
345
388
|
);
|
|
346
389
|
}
|
|
347
390
|
|
|
@@ -366,23 +409,23 @@ export default class ElectronBleTransport {
|
|
|
366
409
|
}
|
|
367
410
|
|
|
368
411
|
if (expectedProtocol === 'V2') {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
412
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
413
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
414
|
+
this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
415
|
+
return 'V2';
|
|
416
|
+
}
|
|
417
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
374
418
|
}
|
|
375
419
|
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// 不能作为最终结论。
|
|
420
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
421
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
379
422
|
const probeOrder: ProtocolType[] =
|
|
380
423
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
381
424
|
|
|
382
425
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
383
426
|
const protocol = probeOrder[i];
|
|
384
427
|
if (i > 0) {
|
|
385
|
-
//
|
|
428
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
386
429
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
387
430
|
}
|
|
388
431
|
const detected =
|
|
@@ -415,16 +458,18 @@ export default class ElectronBleTransport {
|
|
|
415
458
|
}
|
|
416
459
|
|
|
417
460
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
461
|
+
await this.protocolV2Links.invalidateLink(
|
|
462
|
+
uuid,
|
|
463
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
464
|
+
);
|
|
418
465
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
419
466
|
this.v2Assemblers.get(uuid)?.reset();
|
|
420
467
|
this.resetProtocolV2Frames(uuid);
|
|
421
|
-
if (this.
|
|
422
|
-
this.activeProtocolV2Call = null;
|
|
423
|
-
}
|
|
424
|
-
if (this.runPromise) {
|
|
468
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
425
469
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
426
470
|
this.runPromise.reject(error);
|
|
427
471
|
this.runPromise = null;
|
|
472
|
+
this.runPromiseDeviceId = null;
|
|
428
473
|
}
|
|
429
474
|
|
|
430
475
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
@@ -440,9 +485,17 @@ export default class ElectronBleTransport {
|
|
|
440
485
|
this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
441
486
|
}
|
|
442
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);
|
|
443
496
|
await window.desktopApi?.nobleBle?.subscribe(uuid);
|
|
444
497
|
} catch (error) {
|
|
445
|
-
this.Log?.debug(`[Electron BLE]
|
|
498
|
+
this.Log?.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
446
499
|
throw error;
|
|
447
500
|
}
|
|
448
501
|
|
|
@@ -457,11 +510,13 @@ export default class ElectronBleTransport {
|
|
|
457
510
|
|
|
458
511
|
try {
|
|
459
512
|
this.deviceProtocol.set(uuid, 'V1');
|
|
460
|
-
|
|
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 });
|
|
461
516
|
return true;
|
|
462
517
|
} catch (error) {
|
|
463
518
|
this.clearProbeProtocol(uuid, 'V1');
|
|
464
|
-
this.Log?.debug('[Electron BLE] Protocol V1
|
|
519
|
+
this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
465
520
|
return false;
|
|
466
521
|
}
|
|
467
522
|
}
|
|
@@ -489,28 +544,6 @@ export default class ElectronBleTransport {
|
|
|
489
544
|
return detected;
|
|
490
545
|
}
|
|
491
546
|
|
|
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
547
|
private async writeOnce(uuid: string, hexData: string): Promise<void> {
|
|
515
548
|
const nobleBle = window.desktopApi?.nobleBle;
|
|
516
549
|
if (!nobleBle) {
|
|
@@ -520,13 +553,33 @@ export default class ElectronBleTransport {
|
|
|
520
553
|
await nobleBle.write(uuid, hexData);
|
|
521
554
|
}
|
|
522
555
|
|
|
556
|
+
private writeProtocolV2Frame(
|
|
557
|
+
uuid: string,
|
|
558
|
+
frame: Uint8Array,
|
|
559
|
+
context: ProtocolV2CallContext,
|
|
560
|
+
assertCurrentGeneration: () => void
|
|
561
|
+
) {
|
|
562
|
+
return writeProtocolV2BleFrame({
|
|
563
|
+
frame,
|
|
564
|
+
packetCapacity: BLE_PACKET_SIZE,
|
|
565
|
+
assertActive: assertCurrentGeneration,
|
|
566
|
+
signal: context.signal,
|
|
567
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
568
|
+
initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
|
|
569
|
+
burstSize: 1,
|
|
570
|
+
burstPauseMs: BLE_WRITE_DELAY_MS,
|
|
571
|
+
writePacket: packet => this.writeOnce(uuid, bytesToHex(packet)),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
523
575
|
private handleNotification(deviceId: string, hexData: string): void {
|
|
524
576
|
if (hexData === 'PAIRING_REJECTED') {
|
|
525
577
|
this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
|
|
526
|
-
|
|
527
|
-
|
|
578
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
|
|
579
|
+
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
580
|
+
this.rejectProtocolV2Frames(deviceId, error);
|
|
581
|
+
} else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
528
582
|
this.runPromise.reject(error);
|
|
529
|
-
this.rejectAllProtocolV2Frames(error);
|
|
530
583
|
}
|
|
531
584
|
return;
|
|
532
585
|
}
|
|
@@ -545,12 +598,6 @@ export default class ElectronBleTransport {
|
|
|
545
598
|
|
|
546
599
|
private handleProtocolV2Notification(deviceId: string, hexData: string): void {
|
|
547
600
|
try {
|
|
548
|
-
if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
|
|
549
|
-
this.v2Assemblers.get(deviceId)?.reset();
|
|
550
|
-
this.resetProtocolV2Frames(deviceId);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
601
|
const bytes = hexToBytes(hexData);
|
|
555
602
|
if (bytes.length === 0) return;
|
|
556
603
|
|
|
@@ -562,11 +609,8 @@ export default class ElectronBleTransport {
|
|
|
562
609
|
}
|
|
563
610
|
} catch (error) {
|
|
564
611
|
this.Log?.error('[Electron BLE] Protocol V2 notification error:', error);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
this.runPromise.reject(notifyError);
|
|
568
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
569
|
-
}
|
|
612
|
+
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
613
|
+
this.rejectProtocolV2Frames(deviceId, notifyError);
|
|
570
614
|
}
|
|
571
615
|
}
|
|
572
616
|
|
|
@@ -589,21 +633,17 @@ export default class ElectronBleTransport {
|
|
|
589
633
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
590
634
|
}
|
|
591
635
|
|
|
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
636
|
private resetProtocolV2Frames(uuid: string) {
|
|
601
|
-
this.
|
|
602
|
-
this.v2FramePromises.delete(uuid);
|
|
637
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
603
638
|
}
|
|
604
639
|
|
|
605
|
-
private
|
|
606
|
-
|
|
640
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
641
|
+
this.v2FrameQueues.delete(uuid);
|
|
642
|
+
const framePromise = this.v2FramePromises.get(uuid);
|
|
643
|
+
if (framePromise) {
|
|
644
|
+
this.v2FramePromises.delete(uuid);
|
|
645
|
+
framePromise.reject(error);
|
|
646
|
+
}
|
|
607
647
|
}
|
|
608
648
|
|
|
609
649
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -628,13 +668,18 @@ export default class ElectronBleTransport {
|
|
|
628
668
|
|
|
629
669
|
if (result.error) {
|
|
630
670
|
this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
631
|
-
if (this.runPromise) {
|
|
671
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
632
672
|
this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
|
|
633
673
|
}
|
|
634
674
|
return;
|
|
635
675
|
}
|
|
636
676
|
|
|
637
|
-
if (
|
|
677
|
+
if (
|
|
678
|
+
result.isComplete &&
|
|
679
|
+
result.completePacket &&
|
|
680
|
+
this.runPromise &&
|
|
681
|
+
this.runPromiseDeviceId === deviceId
|
|
682
|
+
) {
|
|
638
683
|
this.runPromise.resolve(result.completePacket);
|
|
639
684
|
}
|
|
640
685
|
}
|
|
@@ -660,12 +705,8 @@ export default class ElectronBleTransport {
|
|
|
660
705
|
`Device protocol has not been detected for ${uuid}`
|
|
661
706
|
);
|
|
662
707
|
}
|
|
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);
|
|
708
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
709
|
+
this.Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
669
710
|
}
|
|
670
711
|
|
|
671
712
|
if (protocol === 'V2') {
|
|
@@ -692,6 +733,7 @@ export default class ElectronBleTransport {
|
|
|
692
733
|
const runPromise = createDeferred<Uint8Array | string>();
|
|
693
734
|
runPromise.promise.catch(() => undefined);
|
|
694
735
|
this.runPromise = runPromise;
|
|
736
|
+
this.runPromiseDeviceId = uuid;
|
|
695
737
|
const messages = this._messages;
|
|
696
738
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
697
739
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -736,11 +778,24 @@ export default class ElectronBleTransport {
|
|
|
736
778
|
return check.call(jsonData);
|
|
737
779
|
} catch (e) {
|
|
738
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
|
+
}
|
|
739
793
|
throw e;
|
|
740
794
|
} finally {
|
|
741
795
|
if (timeout) clearTimeout(timeout);
|
|
742
796
|
if (this.runPromise === runPromise) {
|
|
743
797
|
this.runPromise = null;
|
|
798
|
+
this.runPromiseDeviceId = null;
|
|
744
799
|
}
|
|
745
800
|
}
|
|
746
801
|
}
|
|
@@ -755,77 +810,61 @@ export default class ElectronBleTransport {
|
|
|
755
810
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
756
811
|
}
|
|
757
812
|
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
813
|
+
const callOptions = options;
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
return await this.protocolV2Links.call(
|
|
817
|
+
uuid,
|
|
818
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
819
|
+
name,
|
|
820
|
+
data,
|
|
821
|
+
callOptions
|
|
822
|
+
);
|
|
823
|
+
} catch (e) {
|
|
824
|
+
this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
|
|
825
|
+
throw e;
|
|
768
826
|
}
|
|
827
|
+
}
|
|
769
828
|
|
|
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,
|
|
829
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
830
|
+
const generation = this.notificationTokens.get(uuid) ?? 0;
|
|
831
|
+
const assertCurrentGeneration = () => {
|
|
832
|
+
if (this.notificationTokens.get(uuid) !== generation) {
|
|
833
|
+
throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
|
|
834
|
+
}
|
|
781
835
|
};
|
|
782
836
|
|
|
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)) {
|
|
837
|
+
return {
|
|
838
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
839
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
840
|
+
generation,
|
|
841
|
+
prepareCall: () => {
|
|
842
|
+
assertCurrentGeneration();
|
|
812
843
|
this.v2Assemblers.get(uuid)?.reset();
|
|
813
844
|
this.resetProtocolV2Frames(uuid);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
845
|
+
},
|
|
846
|
+
writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
|
|
847
|
+
this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
848
|
+
readFrame: async () => {
|
|
849
|
+
assertCurrentGeneration();
|
|
850
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
851
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
852
|
+
throw new Error('Response is not Uint8Array');
|
|
821
853
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
this.
|
|
827
|
-
}
|
|
828
|
-
|
|
854
|
+
return rxFrame;
|
|
855
|
+
},
|
|
856
|
+
reset: (reason: string) => {
|
|
857
|
+
this.v2Assemblers.get(uuid)?.reset();
|
|
858
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
859
|
+
},
|
|
860
|
+
logger: this.Log,
|
|
861
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
862
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
863
|
+
ERRORS.TypedError(
|
|
864
|
+
HardwareErrorCode.BleTimeoutError,
|
|
865
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
866
|
+
),
|
|
867
|
+
};
|
|
829
868
|
}
|
|
830
869
|
|
|
831
870
|
private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
|