@onekeyfe/hd-transport-web-device 1.2.0-alpha.3 → 1.2.0-alpha.30
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 +267 -18
- package/__tests__/webusb-protocol-v2-timeout.test.ts +288 -0
- package/dist/electron-ble-transport.d.ts +6 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +106 -11
- package/dist/index.js +267 -294
- package/dist/transportLog.d.ts +7 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/webusb.d.ts +15 -7
- 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 +153 -175
- package/src/transportLog.ts +11 -0
- package/src/webusb.ts +171 -179
package/dist/index.js
CHANGED
|
@@ -41,6 +41,14 @@ 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
|
+
|
|
44
52
|
const { parseConfigure: parseConfigure$1, check: check$1, ProtocolV1: ProtocolV1$1 } = transport__default["default"];
|
|
45
53
|
const CONFIGURATION_ID = 1;
|
|
46
54
|
const INTERFACE_ID = 0;
|
|
@@ -52,29 +60,19 @@ const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
|
52
60
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
53
61
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
54
62
|
const PROTOCOL_PROBE_TIMEOUT = 1000;
|
|
55
|
-
const
|
|
56
|
-
function shouldSuppressWebUsbCallLog(name) {
|
|
57
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
58
|
-
return WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
59
|
-
}
|
|
60
|
-
function isLogBlockCommand$1(name) {
|
|
61
|
-
var _a, _b;
|
|
62
|
-
return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
|
|
63
|
-
}
|
|
64
|
-
function shouldBlockWebUsbCallDataLog(name) {
|
|
65
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
66
|
-
return isLogBlockCommand$1(name) || WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
67
|
-
}
|
|
63
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
68
64
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
69
65
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
70
66
|
}
|
|
71
|
-
class WebUsbTransport {
|
|
67
|
+
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
72
68
|
constructor() {
|
|
69
|
+
super({
|
|
70
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
71
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
72
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
73
|
+
});
|
|
73
74
|
this.deviceProtocol = new Map();
|
|
74
75
|
this.deviceProtocolHints = new Map();
|
|
75
|
-
this.protocolV2Assemblers = new Map();
|
|
76
|
-
this.protocolV2Sessions = new Map();
|
|
77
|
-
this.protocolV2ReadTimeouts = new Map();
|
|
78
76
|
this.deviceEndpoints = new Map();
|
|
79
77
|
this.mockSerialPaths = new WeakMap();
|
|
80
78
|
this.mockSerialCounter = 0;
|
|
@@ -101,10 +99,17 @@ class WebUsbTransport {
|
|
|
101
99
|
}
|
|
102
100
|
configureProtocolV2(signedData) {
|
|
103
101
|
var _a;
|
|
102
|
+
const schemaSource = typeof signedData === 'string'
|
|
103
|
+
? signedData
|
|
104
|
+
: (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
|
|
105
|
+
if (schemaSource === this.protocolV2SchemaSource)
|
|
106
|
+
return;
|
|
107
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
104
108
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
110
|
+
if (!hadProtocolV2Schema)
|
|
111
|
+
return;
|
|
112
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] schema link cleanup failed:', error); });
|
|
108
113
|
}
|
|
109
114
|
promptDeviceAccess() {
|
|
110
115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -145,7 +150,10 @@ class WebUsbTransport {
|
|
|
145
150
|
if (!this.usb)
|
|
146
151
|
return [];
|
|
147
152
|
const devices = yield this.usb.getDevices();
|
|
148
|
-
const onekeyDevices = devices.filter(dev =>
|
|
153
|
+
const onekeyDevices = devices.filter(dev => {
|
|
154
|
+
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
155
|
+
return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
156
|
+
});
|
|
149
157
|
this.deviceList = onekeyDevices.map(device => {
|
|
150
158
|
const path = this.getDevicePath(device);
|
|
151
159
|
const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
|
|
@@ -158,10 +166,6 @@ class WebUsbTransport {
|
|
|
158
166
|
commType: 'webusb',
|
|
159
167
|
};
|
|
160
168
|
});
|
|
161
|
-
for (const dev of onekeyDevices) {
|
|
162
|
-
this.Log.debug(`[WebUSB] Device: name="${dev.productName}" serial="${dev.serialNumber}" ` +
|
|
163
|
-
`VID=0x${dev.vendorId.toString(16)} PID=0x${dev.productId.toString(16)}`);
|
|
164
|
-
}
|
|
165
169
|
return this.deviceList;
|
|
166
170
|
});
|
|
167
171
|
}
|
|
@@ -171,6 +175,7 @@ class WebUsbTransport {
|
|
|
171
175
|
if (!input.path)
|
|
172
176
|
return;
|
|
173
177
|
try {
|
|
178
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
174
179
|
yield this.closeOpenDevice(input.path);
|
|
175
180
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
176
181
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
@@ -192,32 +197,45 @@ class WebUsbTransport {
|
|
|
192
197
|
createProtocolMismatchError(expected) {
|
|
193
198
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
194
199
|
}
|
|
200
|
+
createProtocolProbeTimeoutError(expected, attempts) {
|
|
201
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
202
|
+
}
|
|
195
203
|
createProtocolDetectionError() {
|
|
196
204
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
|
|
197
205
|
}
|
|
198
206
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
207
|
+
var _a;
|
|
199
208
|
return __awaiter(this, void 0, void 0, function* () {
|
|
200
209
|
if (expectedProtocol === 'V1') {
|
|
201
210
|
if (yield this.probeProtocolV1(path)) {
|
|
202
211
|
this.deviceProtocol.set(path, 'V1');
|
|
203
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
204
212
|
return 'V1';
|
|
205
213
|
}
|
|
214
|
+
yield this.resetConnectionAfterProbe(path);
|
|
206
215
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
207
216
|
}
|
|
208
217
|
if (expectedProtocol === 'V2') {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
219
|
+
if (yield this.probeProtocolV2(path)) {
|
|
220
|
+
this.deviceProtocol.set(path, 'V2');
|
|
221
|
+
return 'V2';
|
|
222
|
+
}
|
|
223
|
+
yield this.resetConnectionAfterProbe(path);
|
|
224
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
225
|
+
(_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}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
this.deviceProtocol.delete(path);
|
|
229
|
+
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
212
230
|
}
|
|
213
231
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
214
232
|
for (const protocol of probeOrder) {
|
|
215
233
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
216
234
|
if (detected) {
|
|
217
235
|
this.deviceProtocol.set(path, protocol);
|
|
218
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
219
236
|
return protocol;
|
|
220
237
|
}
|
|
238
|
+
yield this.resetConnectionAfterProbe(path);
|
|
221
239
|
}
|
|
222
240
|
this.deviceProtocol.delete(path);
|
|
223
241
|
throw this.createProtocolDetectionError();
|
|
@@ -284,10 +302,9 @@ class WebUsbTransport {
|
|
|
284
302
|
};
|
|
285
303
|
}
|
|
286
304
|
connectToDevice(path, first) {
|
|
287
|
-
var _a
|
|
305
|
+
var _a;
|
|
288
306
|
return __awaiter(this, void 0, void 0, function* () {
|
|
289
307
|
let device = yield this.findDevice(path);
|
|
290
|
-
this.Log.debug('[WebUsbTransport] connecting to device:', device.productName, 'PID:', device.productId);
|
|
291
308
|
if (!device.opened) {
|
|
292
309
|
yield device.open();
|
|
293
310
|
}
|
|
@@ -309,33 +326,30 @@ class WebUsbTransport {
|
|
|
309
326
|
}
|
|
310
327
|
const endpoints = this.discoverEndpoints(device);
|
|
311
328
|
this.deviceEndpoints.set(path, endpoints);
|
|
312
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
313
|
-
this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
314
329
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
315
330
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
316
331
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
317
332
|
});
|
|
318
333
|
}
|
|
319
334
|
closeOpenDevice(path) {
|
|
320
|
-
var _a, _b, _c, _d
|
|
335
|
+
var _a, _b, _c, _d;
|
|
321
336
|
return __awaiter(this, void 0, void 0, function* () {
|
|
322
|
-
(_a = this.
|
|
323
|
-
const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
337
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
324
338
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
325
339
|
return;
|
|
326
340
|
const endpoints = this.deviceEndpoints.get(path);
|
|
327
|
-
const ifaceNum = (
|
|
341
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
328
342
|
try {
|
|
329
343
|
yield current.releaseInterface(ifaceNum);
|
|
330
344
|
}
|
|
331
345
|
catch (error) {
|
|
332
|
-
(
|
|
346
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
333
347
|
}
|
|
334
348
|
try {
|
|
335
349
|
yield current.close();
|
|
336
350
|
}
|
|
337
351
|
catch (error) {
|
|
338
|
-
(
|
|
352
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
339
353
|
}
|
|
340
354
|
});
|
|
341
355
|
}
|
|
@@ -420,19 +434,11 @@ class WebUsbTransport {
|
|
|
420
434
|
return copied.buffer;
|
|
421
435
|
}
|
|
422
436
|
transferOutWithRetry(path, packet) {
|
|
423
|
-
var _a;
|
|
424
437
|
return __awaiter(this, void 0, void 0, function* () {
|
|
425
438
|
let lastError;
|
|
426
439
|
for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
|
|
427
440
|
try {
|
|
428
|
-
|
|
429
|
-
if (!device.opened) {
|
|
430
|
-
yield this.connect(path, false);
|
|
431
|
-
}
|
|
432
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
433
|
-
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
434
|
-
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
435
|
-
yield device.transferOut(endpointOut, transferBuffer);
|
|
441
|
+
yield this.transferOutOnce(path, packet);
|
|
436
442
|
return;
|
|
437
443
|
}
|
|
438
444
|
catch (error) {
|
|
@@ -453,6 +459,19 @@ class WebUsbTransport {
|
|
|
453
459
|
throw lastError;
|
|
454
460
|
});
|
|
455
461
|
}
|
|
462
|
+
transferOutOnce(path, packet) {
|
|
463
|
+
var _a;
|
|
464
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
465
|
+
const device = yield this.findDevice(path);
|
|
466
|
+
if (!device.opened) {
|
|
467
|
+
yield this.connect(path, false);
|
|
468
|
+
}
|
|
469
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
470
|
+
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
471
|
+
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
472
|
+
yield device.transferOut(endpointOut, transferBuffer);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
456
475
|
transferInWithRetry(path, length, cancelToken) {
|
|
457
476
|
var _a;
|
|
458
477
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -492,17 +511,28 @@ class WebUsbTransport {
|
|
|
492
511
|
throw lastError;
|
|
493
512
|
});
|
|
494
513
|
}
|
|
514
|
+
transferInOnce(path, length) {
|
|
515
|
+
var _a;
|
|
516
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
517
|
+
const device = yield this.findDevice(path);
|
|
518
|
+
if (!device.opened) {
|
|
519
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
520
|
+
}
|
|
521
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
522
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
523
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
524
|
+
return this.getTransferInData(result);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
495
527
|
resetConnectionAfterProbe(path) {
|
|
496
|
-
var _a
|
|
528
|
+
var _a;
|
|
497
529
|
return __awaiter(this, void 0, void 0, function* () {
|
|
498
|
-
|
|
499
|
-
this.protocolV2Sessions.delete(path);
|
|
500
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
530
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
501
531
|
try {
|
|
502
532
|
const device = yield this.findDevice(path);
|
|
503
533
|
if (device.opened) {
|
|
504
534
|
const endpoints = this.deviceEndpoints.get(path);
|
|
505
|
-
const ifaceNum = (
|
|
535
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
506
536
|
try {
|
|
507
537
|
yield device.releaseInterface(ifaceNum);
|
|
508
538
|
}
|
|
@@ -537,6 +567,14 @@ class WebUsbTransport {
|
|
|
537
567
|
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
538
568
|
timedOut = true;
|
|
539
569
|
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
570
|
+
if (protocol === 'V1') {
|
|
571
|
+
try {
|
|
572
|
+
yield this.resetConnectionAfterProbe(path);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
540
578
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
541
579
|
}), timeoutMs);
|
|
542
580
|
}),
|
|
@@ -557,8 +595,7 @@ class WebUsbTransport {
|
|
|
557
595
|
yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
|
|
558
596
|
return true;
|
|
559
597
|
}
|
|
560
|
-
catch (
|
|
561
|
-
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
598
|
+
catch (_error) {
|
|
562
599
|
return false;
|
|
563
600
|
}
|
|
564
601
|
});
|
|
@@ -573,7 +610,6 @@ class WebUsbTransport {
|
|
|
573
610
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
574
611
|
logger: this.Log,
|
|
575
612
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
576
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
577
613
|
});
|
|
578
614
|
});
|
|
579
615
|
}
|
|
@@ -590,12 +626,8 @@ class WebUsbTransport {
|
|
|
590
626
|
if (!protocol) {
|
|
591
627
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
592
628
|
}
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
629
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
630
|
+
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
599
631
|
}
|
|
600
632
|
if (protocol === 'V2') {
|
|
601
633
|
return this.callProtocolV2(path, name, data, options);
|
|
@@ -625,67 +657,8 @@ class WebUsbTransport {
|
|
|
625
657
|
});
|
|
626
658
|
}
|
|
627
659
|
callProtocolV2(path, name, data, options) {
|
|
628
|
-
var _a;
|
|
629
660
|
return __awaiter(this, void 0, void 0, function* () {
|
|
630
|
-
|
|
631
|
-
if (!this.messagesV2) {
|
|
632
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
633
|
-
}
|
|
634
|
-
if (!protocolV1Messages) {
|
|
635
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
636
|
-
}
|
|
637
|
-
let session = this.protocolV2Sessions.get(path);
|
|
638
|
-
if (!session) {
|
|
639
|
-
session = new transport.ProtocolV2Session({
|
|
640
|
-
schemas: {
|
|
641
|
-
protocolV1: protocolV1Messages,
|
|
642
|
-
protocolV2: this.messagesV2,
|
|
643
|
-
},
|
|
644
|
-
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
645
|
-
writeFrame: (frame) => this.transferOutWithRetry(path, frame),
|
|
646
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
647
|
-
logger: this.Log,
|
|
648
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
649
|
-
createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
650
|
-
});
|
|
651
|
-
this.protocolV2Sessions.set(path, session);
|
|
652
|
-
}
|
|
653
|
-
this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
654
|
-
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
655
|
-
try {
|
|
656
|
-
return yield session.call(name, data, options);
|
|
657
|
-
}
|
|
658
|
-
finally {
|
|
659
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
receiveProtocolV2Frame(path, timeoutMs) {
|
|
664
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
665
|
-
let assembler = this.protocolV2Assemblers.get(path);
|
|
666
|
-
if (!assembler) {
|
|
667
|
-
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
668
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
669
|
-
}
|
|
670
|
-
let frame = assembler.push(new Uint8Array(0));
|
|
671
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
672
|
-
while (!frame) {
|
|
673
|
-
const cancelToken = { cancelled: false };
|
|
674
|
-
const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
675
|
-
const dataView = deadline
|
|
676
|
-
? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
|
|
677
|
-
cancelToken.cancelled = true;
|
|
678
|
-
})
|
|
679
|
-
: yield transferIn;
|
|
680
|
-
const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
681
|
-
try {
|
|
682
|
-
frame = assembler.push(bytes);
|
|
683
|
-
}
|
|
684
|
-
catch (error) {
|
|
685
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return frame;
|
|
661
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
689
662
|
});
|
|
690
663
|
}
|
|
691
664
|
receiveData(path, timeoutMs) {
|
|
@@ -726,34 +699,55 @@ class WebUsbTransport {
|
|
|
726
699
|
});
|
|
727
700
|
}
|
|
728
701
|
release(path) {
|
|
729
|
-
var _a, _b;
|
|
730
702
|
return __awaiter(this, void 0, void 0, function* () {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
734
|
-
yield device.releaseInterface(ifaceNum);
|
|
735
|
-
yield device.close();
|
|
703
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
704
|
+
yield this.closeOpenDevice(path);
|
|
736
705
|
this.deviceProtocol.delete(path);
|
|
737
706
|
this.deviceProtocolHints.delete(path);
|
|
738
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
739
|
-
this.protocolV2Assemblers.delete(path);
|
|
740
707
|
this.deviceEndpoints.delete(path);
|
|
741
708
|
});
|
|
742
709
|
}
|
|
710
|
+
getProtocolV2UsbSchemas() {
|
|
711
|
+
if (!this.messages || !this.messagesV2) {
|
|
712
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
protocolV1: this.messages,
|
|
716
|
+
protocolV2: this.messagesV2,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
getProtocolV2UsbLogger() {
|
|
720
|
+
return this.Log;
|
|
721
|
+
}
|
|
722
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
724
|
+
yield this.transferOutOnce(path, frame);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
728
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
729
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
730
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
734
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
735
|
+
yield this.closeOpenDevice(path);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
739
|
+
var _a;
|
|
740
|
+
this.deviceProtocol.delete(path);
|
|
741
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
742
|
+
}
|
|
743
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
744
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
745
|
+
}
|
|
743
746
|
getProtocolType(path) {
|
|
744
747
|
return this.deviceProtocol.get(path);
|
|
745
748
|
}
|
|
746
749
|
}
|
|
747
750
|
|
|
748
|
-
const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
|
|
749
|
-
function shouldSuppressHighVolumeCallLog(name) {
|
|
750
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
751
|
-
return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
752
|
-
}
|
|
753
|
-
function isLogBlockCommand(name) {
|
|
754
|
-
var _a, _b;
|
|
755
|
-
return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
|
|
756
|
-
}
|
|
757
751
|
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
758
752
|
function inferProtocolHintFromDeviceName(name) {
|
|
759
753
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
@@ -761,9 +755,6 @@ function inferProtocolHintFromDeviceName(name) {
|
|
|
761
755
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
762
756
|
const BLE_PACKET_SIZE = 192;
|
|
763
757
|
const BLE_WRITE_DELAY_MS = 5;
|
|
764
|
-
const BLE_WRITE_MAX_RETRIES = 3;
|
|
765
|
-
const BLE_WRITE_RETRY_DELAY_MS = 300;
|
|
766
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30000;
|
|
767
758
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
768
759
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
769
760
|
class ElectronBleTransport {
|
|
@@ -778,8 +769,27 @@ class ElectronBleTransport {
|
|
|
778
769
|
this.v2Assemblers = new Map();
|
|
779
770
|
this.v2FrameQueues = new Map();
|
|
780
771
|
this.v2FramePromises = new Map();
|
|
781
|
-
this.
|
|
782
|
-
|
|
772
|
+
this.protocolV2Links = new transport.ProtocolV2LinkManager({
|
|
773
|
+
getSchemas: () => {
|
|
774
|
+
if (!this._messages || !this._messagesV2) {
|
|
775
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
protocolV1: this._messages,
|
|
779
|
+
protocolV2: this._messagesV2,
|
|
780
|
+
};
|
|
781
|
+
},
|
|
782
|
+
classifyError: () => 'link-fatal',
|
|
783
|
+
onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
784
|
+
var _a, _b;
|
|
785
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
786
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
787
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
788
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
789
|
+
yield this.release(uuid);
|
|
790
|
+
}
|
|
791
|
+
}),
|
|
792
|
+
});
|
|
783
793
|
this.notificationCleanups = new Map();
|
|
784
794
|
this.disconnectCleanups = new Map();
|
|
785
795
|
this.notificationTokens = new Map();
|
|
@@ -815,15 +825,14 @@ class ElectronBleTransport {
|
|
|
815
825
|
throw error;
|
|
816
826
|
}
|
|
817
827
|
cleanupDeviceState(deviceId) {
|
|
818
|
-
|
|
828
|
+
this.protocolV2Links
|
|
829
|
+
.invalidateLink(deviceId, 'Electron BLE device state cleaned')
|
|
830
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
|
|
819
831
|
this.connectedDevices.delete(deviceId);
|
|
820
832
|
this.deviceProtocol.delete(deviceId);
|
|
821
833
|
this.v1Buffers.delete(deviceId);
|
|
822
834
|
this.v2Assemblers.delete(deviceId);
|
|
823
835
|
this.resetProtocolV2Frames(deviceId);
|
|
824
|
-
if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
|
|
825
|
-
this.activeProtocolV2Call = null;
|
|
826
|
-
}
|
|
827
836
|
this.notificationTokens.delete(deviceId);
|
|
828
837
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
829
838
|
if (notifyCleanup) {
|
|
@@ -850,9 +859,18 @@ class ElectronBleTransport {
|
|
|
850
859
|
this.configured = true;
|
|
851
860
|
}
|
|
852
861
|
configureProtocolV2(signedData) {
|
|
853
|
-
|
|
862
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
863
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
854
867
|
this._messagesV2 = parseConfigure(signedData);
|
|
855
|
-
|
|
868
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
869
|
+
if (isReconfiguration) {
|
|
870
|
+
this.protocolV2Links
|
|
871
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
872
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
|
|
873
|
+
}
|
|
856
874
|
}
|
|
857
875
|
listen() {
|
|
858
876
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -884,18 +902,20 @@ class ElectronBleTransport {
|
|
|
884
902
|
});
|
|
885
903
|
}
|
|
886
904
|
acquire(input) {
|
|
887
|
-
var _a, _b, _c, _d, _e
|
|
905
|
+
var _a, _b, _c, _d, _e;
|
|
888
906
|
return __awaiter(this, void 0, void 0, function* () {
|
|
889
907
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
890
908
|
if (!uuid) {
|
|
891
909
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
892
910
|
}
|
|
911
|
+
if (this.connectedDevices.has(uuid)) {
|
|
912
|
+
yield this.release(uuid);
|
|
913
|
+
}
|
|
893
914
|
if (forceCleanRunPromise && this.runPromise) {
|
|
894
915
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
895
916
|
this.runPromise.reject(error);
|
|
896
917
|
this.rejectAllProtocolV2Frames(error);
|
|
897
918
|
this.runPromise = null;
|
|
898
|
-
this.activeProtocolV2Call = null;
|
|
899
919
|
}
|
|
900
920
|
try {
|
|
901
921
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -919,7 +939,7 @@ class ElectronBleTransport {
|
|
|
919
939
|
this.handleBluetoothError(error);
|
|
920
940
|
}
|
|
921
941
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
922
|
-
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
|
|
942
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
923
943
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
924
944
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
925
945
|
this.notificationCleanups.set(uuid, cleanup);
|
|
@@ -927,7 +947,7 @@ class ElectronBleTransport {
|
|
|
927
947
|
var _a;
|
|
928
948
|
if (disconnectedDevice.id === uuid) {
|
|
929
949
|
this.cleanupDeviceState(uuid);
|
|
930
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(
|
|
950
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
931
951
|
name: disconnectedDevice.name,
|
|
932
952
|
id: disconnectedDevice.id,
|
|
933
953
|
connectId: disconnectedDevice.id,
|
|
@@ -936,23 +956,18 @@ class ElectronBleTransport {
|
|
|
936
956
|
});
|
|
937
957
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
938
958
|
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
939
|
-
(_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
|
|
940
|
-
name: device.name,
|
|
941
|
-
id: device.id,
|
|
942
|
-
connectId: device.id,
|
|
943
|
-
});
|
|
944
959
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
945
960
|
}
|
|
946
961
|
catch (error) {
|
|
947
|
-
(
|
|
962
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] acquire failed:', error);
|
|
948
963
|
try {
|
|
949
|
-
if (((
|
|
964
|
+
if (((_d = window.desktopApi) === null || _d === void 0 ? void 0 : _d.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
950
965
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
951
966
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
952
967
|
}
|
|
953
968
|
}
|
|
954
969
|
catch (cleanupError) {
|
|
955
|
-
(
|
|
970
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
956
971
|
}
|
|
957
972
|
this.cleanupDeviceState(uuid);
|
|
958
973
|
throw error;
|
|
@@ -963,6 +978,7 @@ class ElectronBleTransport {
|
|
|
963
978
|
var _a, _b;
|
|
964
979
|
return __awaiter(this, void 0, void 0, function* () {
|
|
965
980
|
try {
|
|
981
|
+
yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
966
982
|
if (this.connectedDevices.has(id)) {
|
|
967
983
|
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
968
984
|
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
@@ -1000,9 +1016,12 @@ class ElectronBleTransport {
|
|
|
1000
1016
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1001
1017
|
}
|
|
1002
1018
|
if (expectedProtocol === 'V2') {
|
|
1003
|
-
this.
|
|
1004
|
-
|
|
1005
|
-
|
|
1019
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
1020
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1021
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1022
|
+
return 'V2';
|
|
1023
|
+
}
|
|
1024
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1006
1025
|
}
|
|
1007
1026
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1008
1027
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1036,14 +1055,12 @@ class ElectronBleTransport {
|
|
|
1036
1055
|
});
|
|
1037
1056
|
}
|
|
1038
1057
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1039
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
1058
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1040
1059
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1060
|
+
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1041
1061
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1042
1062
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1043
1063
|
this.resetProtocolV2Frames(uuid);
|
|
1044
|
-
if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
|
|
1045
|
-
this.activeProtocolV2Call = null;
|
|
1046
|
-
}
|
|
1047
1064
|
if (this.runPromise) {
|
|
1048
1065
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1049
1066
|
this.runPromise.reject(error);
|
|
@@ -1056,16 +1073,16 @@ class ElectronBleTransport {
|
|
|
1056
1073
|
}
|
|
1057
1074
|
this.notificationTokens.delete(uuid);
|
|
1058
1075
|
try {
|
|
1059
|
-
yield ((
|
|
1076
|
+
yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
|
|
1060
1077
|
}
|
|
1061
1078
|
catch (error) {
|
|
1062
|
-
(
|
|
1079
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1063
1080
|
}
|
|
1064
1081
|
try {
|
|
1065
|
-
yield ((
|
|
1082
|
+
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
|
|
1066
1083
|
}
|
|
1067
1084
|
catch (error) {
|
|
1068
|
-
(
|
|
1085
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1069
1086
|
throw error;
|
|
1070
1087
|
}
|
|
1071
1088
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1115,57 +1132,39 @@ class ElectronBleTransport {
|
|
|
1115
1132
|
return detected;
|
|
1116
1133
|
});
|
|
1117
1134
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const totalBytes = hexData.length / 2;
|
|
1121
|
-
if (totalBytes <= BLE_PACKET_SIZE) {
|
|
1122
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1123
|
-
yield this.writeWithRetry(uuid, hexData);
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
for (let offset = 0; offset < hexData.length;) {
|
|
1127
|
-
const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
|
|
1128
|
-
const chunkHex = hexData.substring(offset, offset + chunkHexLen);
|
|
1129
|
-
offset += chunkHexLen;
|
|
1130
|
-
yield this.writeWithRetry(uuid, chunkHex);
|
|
1131
|
-
if (offset < hexData.length) {
|
|
1132
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
writeWithRetry(uuid, hexData) {
|
|
1138
|
-
var _a, _b, _c;
|
|
1135
|
+
writeOnce(uuid, hexData) {
|
|
1136
|
+
var _a;
|
|
1139
1137
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1140
|
-
let lastError;
|
|
1141
1138
|
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1142
1139
|
if (!nobleBle) {
|
|
1143
1140
|
throw new Error('Noble BLE API not available');
|
|
1144
1141
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1142
|
+
yield nobleBle.write(uuid, hexData);
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
1146
|
+
return transport.writeProtocolV2BleFrame({
|
|
1147
|
+
frame,
|
|
1148
|
+
packetCapacity: BLE_PACKET_SIZE,
|
|
1149
|
+
assertActive: assertCurrentGeneration,
|
|
1150
|
+
signal: context.signal,
|
|
1151
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1152
|
+
initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
|
|
1153
|
+
burstSize: 1,
|
|
1154
|
+
burstPauseMs: BLE_WRITE_DELAY_MS,
|
|
1155
|
+
writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
|
|
1159
1156
|
});
|
|
1160
1157
|
}
|
|
1161
1158
|
handleNotification(deviceId, hexData) {
|
|
1162
1159
|
var _a, _b;
|
|
1163
1160
|
if (hexData === 'PAIRING_REJECTED') {
|
|
1164
1161
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
|
|
1165
|
-
|
|
1166
|
-
|
|
1162
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
|
|
1163
|
+
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1164
|
+
this.rejectProtocolV2Frames(deviceId, error);
|
|
1165
|
+
}
|
|
1166
|
+
else if (this.runPromise) {
|
|
1167
1167
|
this.runPromise.reject(error);
|
|
1168
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1169
1168
|
}
|
|
1170
1169
|
return;
|
|
1171
1170
|
}
|
|
@@ -1181,13 +1180,8 @@ class ElectronBleTransport {
|
|
|
1181
1180
|
this.handleProtocolV1Notification(deviceId, hexData);
|
|
1182
1181
|
}
|
|
1183
1182
|
handleProtocolV2Notification(deviceId, hexData) {
|
|
1184
|
-
var _a
|
|
1183
|
+
var _a;
|
|
1185
1184
|
try {
|
|
1186
|
-
if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== deviceId) {
|
|
1187
|
-
(_b = this.v2Assemblers.get(deviceId)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
1188
|
-
this.resetProtocolV2Frames(deviceId);
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
1185
|
const bytes = transport.hexToBytes(hexData);
|
|
1192
1186
|
if (bytes.length === 0)
|
|
1193
1187
|
return;
|
|
@@ -1199,12 +1193,9 @@ class ElectronBleTransport {
|
|
|
1199
1193
|
}
|
|
1200
1194
|
}
|
|
1201
1195
|
catch (error) {
|
|
1202
|
-
(
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
this.runPromise.reject(notifyError);
|
|
1206
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
1207
|
-
}
|
|
1196
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 notification error:', error);
|
|
1197
|
+
const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
1198
|
+
this.rejectProtocolV2Frames(deviceId, notifyError);
|
|
1208
1199
|
}
|
|
1209
1200
|
}
|
|
1210
1201
|
getProtocolV2FrameQueue(uuid) {
|
|
@@ -1235,9 +1226,13 @@ class ElectronBleTransport {
|
|
|
1235
1226
|
this.v2FrameQueues.delete(uuid);
|
|
1236
1227
|
this.v2FramePromises.delete(uuid);
|
|
1237
1228
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1229
|
+
rejectProtocolV2Frames(uuid, error) {
|
|
1230
|
+
this.v2FrameQueues.delete(uuid);
|
|
1231
|
+
const framePromise = this.v2FramePromises.get(uuid);
|
|
1232
|
+
if (framePromise) {
|
|
1233
|
+
this.v2FramePromises.delete(uuid);
|
|
1234
|
+
framePromise.reject(error);
|
|
1235
|
+
}
|
|
1241
1236
|
}
|
|
1242
1237
|
readProtocolV2Frame(uuid) {
|
|
1243
1238
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1272,7 +1267,7 @@ class ElectronBleTransport {
|
|
|
1272
1267
|
}
|
|
1273
1268
|
}
|
|
1274
1269
|
call(uuid, name, data, options) {
|
|
1275
|
-
var _a
|
|
1270
|
+
var _a;
|
|
1276
1271
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1277
1272
|
if (!this._messages) {
|
|
1278
1273
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1284,12 +1279,8 @@ class ElectronBleTransport {
|
|
|
1284
1279
|
if (!protocol) {
|
|
1285
1280
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1286
1281
|
}
|
|
1287
|
-
if (shouldSuppressHighVolumeCallLog(name))
|
|
1288
|
-
|
|
1289
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
|
|
1290
|
-
}
|
|
1291
|
-
else {
|
|
1292
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
|
|
1282
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
1283
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
|
|
1293
1284
|
}
|
|
1294
1285
|
if (protocol === 'V2') {
|
|
1295
1286
|
return this.callProtocolV2(uuid, name, data, options);
|
|
@@ -1360,76 +1351,58 @@ class ElectronBleTransport {
|
|
|
1360
1351
|
});
|
|
1361
1352
|
}
|
|
1362
1353
|
callProtocolV2(uuid, name, data, options) {
|
|
1363
|
-
var _a
|
|
1354
|
+
var _a;
|
|
1364
1355
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1365
1356
|
if (!this._messages || !this._messagesV2) {
|
|
1366
1357
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1367
1358
|
}
|
|
1368
|
-
const
|
|
1369
|
-
if (this.runPromise) {
|
|
1370
|
-
if (!forceRun) {
|
|
1371
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
|
|
1372
|
-
}
|
|
1373
|
-
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1374
|
-
this.runPromise.reject(error);
|
|
1375
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1376
|
-
this.runPromise = null;
|
|
1377
|
-
this.activeProtocolV2Call = null;
|
|
1378
|
-
}
|
|
1379
|
-
const runPromise = hdShared.createDeferred();
|
|
1380
|
-
runPromise.promise.catch(() => undefined);
|
|
1381
|
-
this.runPromise = runPromise;
|
|
1382
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1383
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1384
|
-
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1385
|
-
this.resetProtocolV2Frames(uuid);
|
|
1386
|
-
let completed = false;
|
|
1387
|
-
const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : BLE_RESPONSE_TIMEOUT_MS });
|
|
1359
|
+
const callOptions = options;
|
|
1388
1360
|
try {
|
|
1389
|
-
|
|
1390
|
-
schemas: {
|
|
1391
|
-
protocolV1: this._messages,
|
|
1392
|
-
protocolV2: this._messagesV2,
|
|
1393
|
-
},
|
|
1394
|
-
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1395
|
-
writeFrame: (frame) => this.writeWithChunking(uuid, transport.bytesToHex(frame)),
|
|
1396
|
-
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1397
|
-
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
1398
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1399
|
-
throw new Error('Response is not Uint8Array');
|
|
1400
|
-
}
|
|
1401
|
-
return rxFrame;
|
|
1402
|
-
}),
|
|
1403
|
-
logger: this.Log,
|
|
1404
|
-
logPrefix: 'ProtocolV2 BLE',
|
|
1405
|
-
createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
|
|
1406
|
-
});
|
|
1407
|
-
const result = yield session.call(name, data, callOptions);
|
|
1408
|
-
completed = true;
|
|
1409
|
-
return result;
|
|
1361
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
1410
1362
|
}
|
|
1411
1363
|
catch (e) {
|
|
1412
|
-
|
|
1413
|
-
(_c = this.v2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
1414
|
-
this.resetProtocolV2Frames(uuid);
|
|
1415
|
-
}
|
|
1416
|
-
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1364
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1417
1365
|
throw e;
|
|
1418
1366
|
}
|
|
1419
|
-
finally {
|
|
1420
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1421
|
-
if (!completed) {
|
|
1422
|
-
(_e = this.v2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
|
|
1423
|
-
}
|
|
1424
|
-
this.resetProtocolV2Frames(uuid);
|
|
1425
|
-
this.activeProtocolV2Call = null;
|
|
1426
|
-
}
|
|
1427
|
-
if (this.runPromise === runPromise) {
|
|
1428
|
-
this.runPromise = null;
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
1367
|
});
|
|
1432
1368
|
}
|
|
1369
|
+
createProtocolV2Adapter(uuid) {
|
|
1370
|
+
var _a;
|
|
1371
|
+
const generation = (_a = this.notificationTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
1372
|
+
const assertCurrentGeneration = () => {
|
|
1373
|
+
if (this.notificationTokens.get(uuid) !== generation) {
|
|
1374
|
+
throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
return {
|
|
1378
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1379
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1380
|
+
generation,
|
|
1381
|
+
prepareCall: () => {
|
|
1382
|
+
var _a;
|
|
1383
|
+
assertCurrentGeneration();
|
|
1384
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1385
|
+
this.resetProtocolV2Frames(uuid);
|
|
1386
|
+
},
|
|
1387
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
1388
|
+
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1389
|
+
assertCurrentGeneration();
|
|
1390
|
+
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
1391
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1392
|
+
throw new Error('Response is not Uint8Array');
|
|
1393
|
+
}
|
|
1394
|
+
return rxFrame;
|
|
1395
|
+
}),
|
|
1396
|
+
reset: (reason) => {
|
|
1397
|
+
var _a;
|
|
1398
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1399
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1400
|
+
},
|
|
1401
|
+
logger: this.Log,
|
|
1402
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
1403
|
+
createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1433
1406
|
processProtocolV1Notification(deviceId, hexData) {
|
|
1434
1407
|
try {
|
|
1435
1408
|
if (typeof hexData !== 'string') {
|