@onekeyfe/hd-transport-web-device 1.2.0-alpha.12 → 1.2.0-alpha.121
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 +373 -22
- package/__tests__/webusb-protocol-v2-timeout.test.ts +386 -0
- package/dist/ble-packet-capacity.d.ts +2 -0
- package/dist/ble-packet-capacity.d.ts.map +1 -0
- package/dist/electron-ble-transport.d.ts +15 -3
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +109 -12
- package/dist/index.js +344 -223
- package/dist/transportLog.d.ts +1 -6
- package/dist/transportLog.d.ts.map +1 -1
- 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/ble-packet-capacity.ts +13 -0
- package/src/electron-ble-transport.ts +231 -85
- package/src/transportLog.ts +1 -11
- package/src/webusb.ts +182 -181
package/dist/index.js
CHANGED
|
@@ -41,14 +41,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
41
41
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
|
|
45
|
-
function shouldSuppressHighVolumeCallLog(name) {
|
|
46
|
-
return HIGH_VOLUME_CALLS.has(name);
|
|
47
|
-
}
|
|
48
|
-
function createTransportCallLog(name, protocol) {
|
|
49
|
-
return { name, protocol };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
const { parseConfigure: parseConfigure$1, check: check$1, ProtocolV1: ProtocolV1$1 } = transport__default["default"];
|
|
53
45
|
const CONFIGURATION_ID = 1;
|
|
54
46
|
const INTERFACE_ID = 0;
|
|
@@ -59,20 +51,22 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
|
59
51
|
const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
60
52
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
61
53
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
62
|
-
const
|
|
54
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
56
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
63
57
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
64
58
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
65
59
|
}
|
|
66
|
-
class WebUsbTransport {
|
|
60
|
+
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
67
61
|
constructor() {
|
|
62
|
+
super({
|
|
63
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
64
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
65
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
66
|
+
});
|
|
68
67
|
this.deviceProtocol = new Map();
|
|
69
68
|
this.deviceProtocolHints = new Map();
|
|
70
|
-
this.protocolV2Assemblers = new Map();
|
|
71
|
-
this.protocolV2Sessions = new Map();
|
|
72
|
-
this.protocolV2ReadTimeouts = new Map();
|
|
73
69
|
this.deviceEndpoints = new Map();
|
|
74
|
-
this.mockSerialPaths = new WeakMap();
|
|
75
|
-
this.mockSerialCounter = 0;
|
|
76
70
|
this.name = 'WebUsbTransport';
|
|
77
71
|
this.stopped = false;
|
|
78
72
|
this.configured = false;
|
|
@@ -95,9 +89,18 @@ class WebUsbTransport {
|
|
|
95
89
|
this.messages = messages;
|
|
96
90
|
}
|
|
97
91
|
configureProtocolV2(signedData) {
|
|
92
|
+
var _a;
|
|
93
|
+
const schemaSource = typeof signedData === 'string'
|
|
94
|
+
? signedData
|
|
95
|
+
: (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
|
|
96
|
+
if (schemaSource === this.protocolV2SchemaSource)
|
|
97
|
+
return;
|
|
98
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
98
99
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
99
|
-
this.
|
|
100
|
-
|
|
100
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
101
|
+
if (!hadProtocolV2Schema)
|
|
102
|
+
return;
|
|
103
|
+
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); });
|
|
101
104
|
}
|
|
102
105
|
promptDeviceAccess() {
|
|
103
106
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -119,28 +122,18 @@ class WebUsbTransport {
|
|
|
119
122
|
return this.deviceList;
|
|
120
123
|
});
|
|
121
124
|
}
|
|
122
|
-
getDevicePath(device) {
|
|
123
|
-
var _a;
|
|
124
|
-
if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
|
|
125
|
-
return device.serialNumber;
|
|
126
|
-
}
|
|
127
|
-
let path = this.mockSerialPaths.get(device);
|
|
128
|
-
if (!path) {
|
|
129
|
-
this.mockSerialCounter += 1;
|
|
130
|
-
path = `mock-serial:${device.vendorId.toString(16)}:${device.productId.toString(16)}:${this.mockSerialCounter}`;
|
|
131
|
-
this.mockSerialPaths.set(device, path);
|
|
132
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUSB] device has no serial number, using mock path: ${path}`);
|
|
133
|
-
}
|
|
134
|
-
return path;
|
|
135
|
-
}
|
|
136
125
|
getConnectedDevices() {
|
|
137
126
|
return __awaiter(this, void 0, void 0, function* () {
|
|
138
127
|
if (!this.usb)
|
|
139
128
|
return [];
|
|
140
129
|
const devices = yield this.usb.getDevices();
|
|
141
|
-
const onekeyDevices = devices.filter(dev =>
|
|
130
|
+
const onekeyDevices = devices.filter(dev => {
|
|
131
|
+
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
132
|
+
const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
|
|
133
|
+
return isOneKey && hasSerialNumber && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
134
|
+
});
|
|
142
135
|
this.deviceList = onekeyDevices.map(device => {
|
|
143
|
-
const path =
|
|
136
|
+
const path = device.serialNumber;
|
|
144
137
|
const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
|
|
145
138
|
if (protocolHint) {
|
|
146
139
|
this.deviceProtocolHints.set(path, protocolHint);
|
|
@@ -155,17 +148,18 @@ class WebUsbTransport {
|
|
|
155
148
|
});
|
|
156
149
|
}
|
|
157
150
|
acquire(input) {
|
|
158
|
-
var _a, _b, _c;
|
|
151
|
+
var _a, _b, _c, _d;
|
|
159
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
160
153
|
if (!input.path)
|
|
161
154
|
return;
|
|
162
155
|
try {
|
|
156
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
163
157
|
yield this.closeOpenDevice(input.path);
|
|
164
158
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
165
159
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
166
160
|
const protocolHint = input.expectedProtocol
|
|
167
161
|
? undefined
|
|
168
|
-
: (_c = this.deviceProtocolHints.get(input.path)) !== null &&
|
|
162
|
+
: (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
|
|
169
163
|
if (protocolHint) {
|
|
170
164
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
171
165
|
}
|
|
@@ -174,6 +168,7 @@ class WebUsbTransport {
|
|
|
174
168
|
}
|
|
175
169
|
catch (e) {
|
|
176
170
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
171
|
+
yield this.closeOpenDevice(input.path);
|
|
177
172
|
throw e;
|
|
178
173
|
}
|
|
179
174
|
});
|
|
@@ -181,29 +176,53 @@ class WebUsbTransport {
|
|
|
181
176
|
createProtocolMismatchError(expected) {
|
|
182
177
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
183
178
|
}
|
|
179
|
+
createProtocolProbeTimeoutError(expected, attempts) {
|
|
180
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
181
|
+
}
|
|
184
182
|
createProtocolDetectionError() {
|
|
185
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
183
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
186
184
|
}
|
|
187
185
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
186
|
+
var _a;
|
|
188
187
|
return __awaiter(this, void 0, void 0, function* () {
|
|
189
188
|
if (expectedProtocol === 'V1') {
|
|
190
189
|
if (yield this.probeProtocolV1(path)) {
|
|
191
190
|
this.deviceProtocol.set(path, 'V1');
|
|
192
191
|
return 'V1';
|
|
193
192
|
}
|
|
193
|
+
yield this.closeConnectionAfterProbe(path);
|
|
194
194
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
195
195
|
}
|
|
196
196
|
if (expectedProtocol === 'V2') {
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
198
|
+
if (yield this.probeProtocolV2(path)) {
|
|
199
|
+
this.deviceProtocol.set(path, 'V2');
|
|
200
|
+
return 'V2';
|
|
201
|
+
}
|
|
202
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
203
|
+
yield this.resetConnectionAfterProbe(path);
|
|
204
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 probe timed out, retrying ${attempt + 1}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
yield this.closeConnectionAfterProbe(path);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.deviceProtocol.delete(path);
|
|
211
|
+
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
199
212
|
}
|
|
200
213
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
201
|
-
for (const protocol of probeOrder) {
|
|
214
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
202
215
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
203
216
|
if (detected) {
|
|
204
217
|
this.deviceProtocol.set(path, protocol);
|
|
205
218
|
return protocol;
|
|
206
219
|
}
|
|
220
|
+
if (index < probeOrder.length - 1) {
|
|
221
|
+
yield this.resetConnectionAfterProbe(path);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
yield this.closeConnectionAfterProbe(path);
|
|
225
|
+
}
|
|
207
226
|
}
|
|
208
227
|
this.deviceProtocol.delete(path);
|
|
209
228
|
throw this.createProtocolDetectionError();
|
|
@@ -270,7 +289,7 @@ class WebUsbTransport {
|
|
|
270
289
|
};
|
|
271
290
|
}
|
|
272
291
|
connectToDevice(path, first) {
|
|
273
|
-
var _a
|
|
292
|
+
var _a;
|
|
274
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
275
294
|
let device = yield this.findDevice(path);
|
|
276
295
|
if (!device.opened) {
|
|
@@ -294,33 +313,30 @@ class WebUsbTransport {
|
|
|
294
313
|
}
|
|
295
314
|
const endpoints = this.discoverEndpoints(device);
|
|
296
315
|
this.deviceEndpoints.set(path, endpoints);
|
|
297
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
298
|
-
this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
299
316
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
300
317
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
301
318
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
302
319
|
});
|
|
303
320
|
}
|
|
304
321
|
closeOpenDevice(path) {
|
|
305
|
-
var _a, _b, _c, _d
|
|
322
|
+
var _a, _b, _c, _d;
|
|
306
323
|
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
-
(_a = this.
|
|
308
|
-
const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
324
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
309
325
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
310
326
|
return;
|
|
311
327
|
const endpoints = this.deviceEndpoints.get(path);
|
|
312
|
-
const ifaceNum = (
|
|
328
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
313
329
|
try {
|
|
314
330
|
yield current.releaseInterface(ifaceNum);
|
|
315
331
|
}
|
|
316
332
|
catch (error) {
|
|
317
|
-
(
|
|
333
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
318
334
|
}
|
|
319
335
|
try {
|
|
320
336
|
yield current.close();
|
|
321
337
|
}
|
|
322
338
|
catch (error) {
|
|
323
|
-
(
|
|
339
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
324
340
|
}
|
|
325
341
|
});
|
|
326
342
|
}
|
|
@@ -482,34 +498,33 @@ class WebUsbTransport {
|
|
|
482
498
|
throw lastError;
|
|
483
499
|
});
|
|
484
500
|
}
|
|
485
|
-
|
|
486
|
-
var _a
|
|
501
|
+
transferInOnce(path, length) {
|
|
502
|
+
var _a;
|
|
487
503
|
return __awaiter(this, void 0, void 0, function* () {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
try {
|
|
492
|
-
const device = yield this.findDevice(path);
|
|
493
|
-
if (device.opened) {
|
|
494
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
495
|
-
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
496
|
-
try {
|
|
497
|
-
yield device.releaseInterface(ifaceNum);
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
501
|
-
}
|
|
502
|
-
yield device.close();
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
catch (error) {
|
|
506
|
-
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
504
|
+
const device = yield this.findDevice(path);
|
|
505
|
+
if (!device.opened) {
|
|
506
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
507
507
|
}
|
|
508
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
509
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
510
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
511
|
+
return this.getTransferInData(result);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
closeConnectionAfterProbe(path) {
|
|
515
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
516
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
517
|
+
yield this.closeOpenDevice(path);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
resetConnectionAfterProbe(path) {
|
|
521
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
522
|
+
yield this.closeConnectionAfterProbe(path);
|
|
508
523
|
yield this.getConnectedDevices();
|
|
509
524
|
yield this.connect(path, false);
|
|
510
525
|
});
|
|
511
526
|
}
|
|
512
|
-
withProtocolReadTimeout(
|
|
527
|
+
withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
|
|
513
528
|
return __awaiter(this, void 0, void 0, function* () {
|
|
514
529
|
let timer;
|
|
515
530
|
let timedOut = false;
|
|
@@ -524,11 +539,19 @@ class WebUsbTransport {
|
|
|
524
539
|
return yield Promise.race([
|
|
525
540
|
guardedPromise,
|
|
526
541
|
new Promise((_, reject) => {
|
|
527
|
-
timer = setTimeout(() => {
|
|
542
|
+
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
528
543
|
timedOut = true;
|
|
529
544
|
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
545
|
+
if (protocol === 'V1') {
|
|
546
|
+
try {
|
|
547
|
+
yield this.resetConnectionAfterProbe(path);
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
530
553
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
531
|
-
}, timeoutMs);
|
|
554
|
+
}), timeoutMs);
|
|
532
555
|
}),
|
|
533
556
|
]);
|
|
534
557
|
}
|
|
@@ -544,7 +567,9 @@ class WebUsbTransport {
|
|
|
544
567
|
return false;
|
|
545
568
|
}
|
|
546
569
|
try {
|
|
547
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
570
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
571
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
572
|
+
});
|
|
548
573
|
return true;
|
|
549
574
|
}
|
|
550
575
|
catch (_error) {
|
|
@@ -559,10 +584,9 @@ class WebUsbTransport {
|
|
|
559
584
|
}
|
|
560
585
|
return transport.probeProtocolV2({
|
|
561
586
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
562
|
-
timeoutMs:
|
|
587
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
563
588
|
logger: this.Log,
|
|
564
589
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
565
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
566
590
|
});
|
|
567
591
|
});
|
|
568
592
|
}
|
|
@@ -579,9 +603,6 @@ class WebUsbTransport {
|
|
|
579
603
|
if (!protocol) {
|
|
580
604
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
581
605
|
}
|
|
582
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
583
|
-
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
584
|
-
}
|
|
585
606
|
if (protocol === 'V2') {
|
|
586
607
|
return this.callProtocolV2(path, name, data, options);
|
|
587
608
|
}
|
|
@@ -610,67 +631,8 @@ class WebUsbTransport {
|
|
|
610
631
|
});
|
|
611
632
|
}
|
|
612
633
|
callProtocolV2(path, name, data, options) {
|
|
613
|
-
var _a;
|
|
614
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
615
|
-
const protocolV1Messages = this.messages;
|
|
616
|
-
if (!this.messagesV2) {
|
|
617
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
618
|
-
}
|
|
619
|
-
if (!protocolV1Messages) {
|
|
620
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
621
|
-
}
|
|
622
|
-
let session = this.protocolV2Sessions.get(path);
|
|
623
|
-
if (!session) {
|
|
624
|
-
session = new transport.ProtocolV2Session({
|
|
625
|
-
schemas: {
|
|
626
|
-
protocolV1: protocolV1Messages,
|
|
627
|
-
protocolV2: this.messagesV2,
|
|
628
|
-
},
|
|
629
|
-
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
630
|
-
writeFrame: (frame) => this.transferOutOnce(path, frame),
|
|
631
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
632
|
-
logger: this.Log,
|
|
633
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
634
|
-
createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
635
|
-
});
|
|
636
|
-
this.protocolV2Sessions.set(path, session);
|
|
637
|
-
}
|
|
638
|
-
this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
639
|
-
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
640
|
-
try {
|
|
641
|
-
return yield session.call(name, data, options);
|
|
642
|
-
}
|
|
643
|
-
finally {
|
|
644
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
receiveProtocolV2Frame(path, timeoutMs) {
|
|
649
634
|
return __awaiter(this, void 0, void 0, function* () {
|
|
650
|
-
|
|
651
|
-
if (!assembler) {
|
|
652
|
-
assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
653
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
654
|
-
}
|
|
655
|
-
let frame = assembler.push(new Uint8Array(0));
|
|
656
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
657
|
-
while (!frame) {
|
|
658
|
-
const cancelToken = { cancelled: false };
|
|
659
|
-
const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
660
|
-
const dataView = deadline
|
|
661
|
-
? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
|
|
662
|
-
cancelToken.cancelled = true;
|
|
663
|
-
})
|
|
664
|
-
: yield transferIn;
|
|
665
|
-
const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
666
|
-
try {
|
|
667
|
-
frame = assembler.push(bytes);
|
|
668
|
-
}
|
|
669
|
-
catch (error) {
|
|
670
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return frame;
|
|
635
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
674
636
|
});
|
|
675
637
|
}
|
|
676
638
|
receiveData(path, timeoutMs) {
|
|
@@ -711,43 +673,84 @@ class WebUsbTransport {
|
|
|
711
673
|
});
|
|
712
674
|
}
|
|
713
675
|
release(path) {
|
|
714
|
-
var _a, _b;
|
|
715
676
|
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
719
|
-
yield device.releaseInterface(ifaceNum);
|
|
720
|
-
yield device.close();
|
|
677
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
678
|
+
yield this.closeOpenDevice(path);
|
|
721
679
|
this.deviceProtocol.delete(path);
|
|
722
680
|
this.deviceProtocolHints.delete(path);
|
|
723
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
724
|
-
this.protocolV2Assemblers.delete(path);
|
|
725
681
|
this.deviceEndpoints.delete(path);
|
|
726
682
|
});
|
|
727
683
|
}
|
|
684
|
+
getProtocolV2UsbSchemas() {
|
|
685
|
+
if (!this.messages || !this.messagesV2) {
|
|
686
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
protocolV1: this.messages,
|
|
690
|
+
protocolV2: this.messagesV2,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
getProtocolV2UsbLogger() {
|
|
694
|
+
return this.Log;
|
|
695
|
+
}
|
|
696
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
697
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
698
|
+
yield this.transferOutOnce(path, frame);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
702
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
703
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
704
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
708
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
709
|
+
yield this.closeOpenDevice(path);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
713
|
+
var _a;
|
|
714
|
+
this.deviceProtocol.delete(path);
|
|
715
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
716
|
+
}
|
|
717
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
718
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
719
|
+
}
|
|
728
720
|
getProtocolType(path) {
|
|
729
721
|
return this.deviceProtocol.get(path);
|
|
730
722
|
}
|
|
731
723
|
}
|
|
732
724
|
|
|
725
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
726
|
+
function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
|
|
727
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
728
|
+
return fallbackPacketCapacity;
|
|
729
|
+
}
|
|
730
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
733
|
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
734
734
|
function inferProtocolHintFromDeviceName(name) {
|
|
735
735
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
736
736
|
}
|
|
737
737
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
738
|
-
const
|
|
738
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
739
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
739
740
|
const BLE_WRITE_DELAY_MS = 5;
|
|
740
|
-
const
|
|
741
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
741
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
|
|
742
742
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
743
743
|
class ElectronBleTransport {
|
|
744
744
|
constructor() {
|
|
745
745
|
this.name = 'ElectronBleTransport';
|
|
746
746
|
this.configured = false;
|
|
747
747
|
this.runPromise = null;
|
|
748
|
+
this.runPromiseDeviceId = null;
|
|
748
749
|
this.connectedDevices = new Set();
|
|
749
750
|
this.deviceProtocol = new Map();
|
|
750
751
|
this.deviceProtocolHints = new Map();
|
|
752
|
+
this.deviceMtus = new Map();
|
|
753
|
+
this.devicePacketCapacities = new Map();
|
|
751
754
|
this.v1Buffers = new Map();
|
|
752
755
|
this.v2Assemblers = new Map();
|
|
753
756
|
this.v2FrameQueues = new Map();
|
|
@@ -769,11 +772,13 @@ class ElectronBleTransport {
|
|
|
769
772
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
770
773
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
771
774
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
772
|
-
yield this.
|
|
775
|
+
yield this.releaseNative(uuid);
|
|
773
776
|
}
|
|
774
777
|
}),
|
|
775
778
|
});
|
|
779
|
+
this.warnedMissingRelease = false;
|
|
776
780
|
this.notificationCleanups = new Map();
|
|
781
|
+
this.mtuCleanups = new Map();
|
|
777
782
|
this.disconnectCleanups = new Map();
|
|
778
783
|
this.notificationTokens = new Map();
|
|
779
784
|
this.nextNotificationToken = 1;
|
|
@@ -813,15 +818,27 @@ class ElectronBleTransport {
|
|
|
813
818
|
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
|
|
814
819
|
this.connectedDevices.delete(deviceId);
|
|
815
820
|
this.deviceProtocol.delete(deviceId);
|
|
821
|
+
this.deviceMtus.delete(deviceId);
|
|
822
|
+
this.devicePacketCapacities.delete(deviceId);
|
|
816
823
|
this.v1Buffers.delete(deviceId);
|
|
817
824
|
this.v2Assemblers.delete(deviceId);
|
|
818
825
|
this.resetProtocolV2Frames(deviceId);
|
|
819
826
|
this.notificationTokens.delete(deviceId);
|
|
827
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
828
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
|
|
829
|
+
this.runPromise = null;
|
|
830
|
+
this.runPromiseDeviceId = null;
|
|
831
|
+
}
|
|
820
832
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
821
833
|
if (notifyCleanup) {
|
|
822
834
|
notifyCleanup();
|
|
823
835
|
this.notificationCleanups.delete(deviceId);
|
|
824
836
|
}
|
|
837
|
+
const mtuCleanup = this.mtuCleanups.get(deviceId);
|
|
838
|
+
if (mtuCleanup) {
|
|
839
|
+
mtuCleanup();
|
|
840
|
+
this.mtuCleanups.delete(deviceId);
|
|
841
|
+
}
|
|
825
842
|
const disconnectCleanup = this.disconnectCleanups.get(deviceId);
|
|
826
843
|
if (disconnectCleanup) {
|
|
827
844
|
disconnectCleanup();
|
|
@@ -842,10 +859,18 @@ class ElectronBleTransport {
|
|
|
842
859
|
this.configured = true;
|
|
843
860
|
}
|
|
844
861
|
configureProtocolV2(signedData) {
|
|
862
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
863
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
845
867
|
this._messagesV2 = parseConfigure(signedData);
|
|
846
|
-
this.
|
|
847
|
-
|
|
848
|
-
|
|
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
|
+
}
|
|
849
874
|
}
|
|
850
875
|
listen() {
|
|
851
876
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -877,7 +902,7 @@ class ElectronBleTransport {
|
|
|
877
902
|
});
|
|
878
903
|
}
|
|
879
904
|
acquire(input) {
|
|
880
|
-
var _a, _b, _c, _d, _e, _f;
|
|
905
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
881
906
|
return __awaiter(this, void 0, void 0, function* () {
|
|
882
907
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
883
908
|
if (!uuid) {
|
|
@@ -889,8 +914,8 @@ class ElectronBleTransport {
|
|
|
889
914
|
if (forceCleanRunPromise && this.runPromise) {
|
|
890
915
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
891
916
|
this.runPromise.reject(error);
|
|
892
|
-
this.rejectAllProtocolV2Frames(error);
|
|
893
917
|
this.runPromise = null;
|
|
918
|
+
this.runPromiseDeviceId = null;
|
|
894
919
|
}
|
|
895
920
|
try {
|
|
896
921
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -902,7 +927,7 @@ class ElectronBleTransport {
|
|
|
902
927
|
}
|
|
903
928
|
const protocolHint = expectedProtocol
|
|
904
929
|
? undefined
|
|
905
|
-
: (_b = this.deviceProtocolHints.get(uuid)) !== null &&
|
|
930
|
+
: (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
|
|
906
931
|
if (protocolHint) {
|
|
907
932
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
908
933
|
}
|
|
@@ -913,16 +938,31 @@ class ElectronBleTransport {
|
|
|
913
938
|
catch (error) {
|
|
914
939
|
this.handleBluetoothError(error);
|
|
915
940
|
}
|
|
941
|
+
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
942
|
+
if (mtuCleanup) {
|
|
943
|
+
this.mtuCleanups.set(uuid, mtuCleanup);
|
|
944
|
+
}
|
|
916
945
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
917
|
-
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
|
|
946
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
918
947
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
948
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
919
949
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
920
950
|
this.notificationCleanups.set(uuid, cleanup);
|
|
951
|
+
const connectionToken = this.notificationTokens.get(uuid);
|
|
952
|
+
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
953
|
+
if (protocolType === 'V2') {
|
|
954
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
|
|
955
|
+
writeMode: 'withoutResponse',
|
|
956
|
+
negotiatedMtu: this.deviceMtus.get(uuid),
|
|
957
|
+
packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
921
960
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
922
961
|
var _a;
|
|
923
|
-
if (disconnectedDevice.id === uuid
|
|
962
|
+
if (disconnectedDevice.id === uuid &&
|
|
963
|
+
this.notificationTokens.get(uuid) === connectionToken) {
|
|
924
964
|
this.cleanupDeviceState(uuid);
|
|
925
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(
|
|
965
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
926
966
|
name: disconnectedDevice.name,
|
|
927
967
|
id: disconnectedDevice.id,
|
|
928
968
|
connectId: disconnectedDevice.id,
|
|
@@ -930,35 +970,46 @@ class ElectronBleTransport {
|
|
|
930
970
|
}
|
|
931
971
|
});
|
|
932
972
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
933
|
-
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
934
|
-
(_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
|
|
935
|
-
name: device.name,
|
|
936
|
-
id: device.id,
|
|
937
|
-
connectId: device.id,
|
|
938
|
-
});
|
|
939
973
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
940
974
|
}
|
|
941
975
|
catch (error) {
|
|
942
|
-
(
|
|
976
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
|
|
943
977
|
try {
|
|
944
|
-
if (((
|
|
978
|
+
if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
945
979
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
946
980
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
947
981
|
}
|
|
948
982
|
}
|
|
949
983
|
catch (cleanupError) {
|
|
950
|
-
(
|
|
984
|
+
(_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
951
985
|
}
|
|
952
986
|
this.cleanupDeviceState(uuid);
|
|
953
987
|
throw error;
|
|
954
988
|
}
|
|
955
989
|
});
|
|
956
990
|
}
|
|
957
|
-
release(id) {
|
|
958
|
-
var _a
|
|
991
|
+
release(id, _onclose, keepSession) {
|
|
992
|
+
var _a;
|
|
959
993
|
return __awaiter(this, void 0, void 0, function* () {
|
|
960
994
|
try {
|
|
961
995
|
yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
996
|
+
yield this.releaseLogical(id, keepSession);
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
|
|
1000
|
+
this.cleanupDeviceState(id);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
disconnect(id) {
|
|
1005
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1006
|
+
return this.releaseNative(id);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
releaseNative(id) {
|
|
1010
|
+
var _a, _b;
|
|
1011
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1012
|
+
try {
|
|
962
1013
|
if (this.connectedDevices.has(id)) {
|
|
963
1014
|
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
964
1015
|
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
@@ -973,11 +1024,35 @@ class ElectronBleTransport {
|
|
|
973
1024
|
}
|
|
974
1025
|
});
|
|
975
1026
|
}
|
|
1027
|
+
releaseLogical(id, keepSession) {
|
|
1028
|
+
var _a, _b, _c, _d;
|
|
1029
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1030
|
+
try {
|
|
1031
|
+
if (!this.connectedDevices.has(id))
|
|
1032
|
+
return;
|
|
1033
|
+
this.cleanupDeviceState(id);
|
|
1034
|
+
const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
|
|
1035
|
+
if (!release) {
|
|
1036
|
+
if (!this.warnedMissingRelease) {
|
|
1037
|
+
this.warnedMissingRelease = true;
|
|
1038
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
|
|
1039
|
+
'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
yield release(id, keepSession);
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
|
|
1047
|
+
this.cleanupDeviceState(id);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
976
1051
|
createProtocolMismatchError(expected) {
|
|
977
1052
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
978
1053
|
}
|
|
979
1054
|
createProtocolDetectionError() {
|
|
980
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1055
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
981
1056
|
}
|
|
982
1057
|
clearProbeProtocol(uuid, protocol) {
|
|
983
1058
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -996,9 +1071,12 @@ class ElectronBleTransport {
|
|
|
996
1071
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
997
1072
|
}
|
|
998
1073
|
if (expectedProtocol === 'V2') {
|
|
999
|
-
this.
|
|
1000
|
-
|
|
1001
|
-
|
|
1074
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
1075
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1076
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1077
|
+
return 'V2';
|
|
1078
|
+
}
|
|
1079
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1002
1080
|
}
|
|
1003
1081
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1004
1082
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1032,16 +1110,17 @@ class ElectronBleTransport {
|
|
|
1032
1110
|
});
|
|
1033
1111
|
}
|
|
1034
1112
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1035
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1113
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1036
1114
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1037
1115
|
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1038
1116
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1039
1117
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1040
1118
|
this.resetProtocolV2Frames(uuid);
|
|
1041
|
-
if (this.runPromise) {
|
|
1119
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1042
1120
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1043
1121
|
this.runPromise.reject(error);
|
|
1044
1122
|
this.runPromise = null;
|
|
1123
|
+
this.runPromiseDeviceId = null;
|
|
1045
1124
|
}
|
|
1046
1125
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1047
1126
|
if (notifyCleanup) {
|
|
@@ -1056,10 +1135,20 @@ class ElectronBleTransport {
|
|
|
1056
1135
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1057
1136
|
}
|
|
1058
1137
|
try {
|
|
1059
|
-
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.
|
|
1138
|
+
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
|
|
1060
1139
|
}
|
|
1061
1140
|
catch (error) {
|
|
1062
|
-
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE]
|
|
1141
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
1142
|
+
}
|
|
1143
|
+
this.connectedDevices.delete(uuid);
|
|
1144
|
+
try {
|
|
1145
|
+
yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
|
|
1146
|
+
this.connectedDevices.add(uuid);
|
|
1147
|
+
yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
|
|
1148
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
1149
|
+
}
|
|
1150
|
+
catch (error) {
|
|
1151
|
+
(_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
1063
1152
|
throw error;
|
|
1064
1153
|
}
|
|
1065
1154
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1074,12 +1163,12 @@ class ElectronBleTransport {
|
|
|
1074
1163
|
}
|
|
1075
1164
|
try {
|
|
1076
1165
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1077
|
-
yield this.callProtocolV1(uuid, '
|
|
1166
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1078
1167
|
return true;
|
|
1079
1168
|
}
|
|
1080
1169
|
catch (error) {
|
|
1081
1170
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1082
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1171
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1083
1172
|
return false;
|
|
1084
1173
|
}
|
|
1085
1174
|
});
|
|
@@ -1109,25 +1198,6 @@ class ElectronBleTransport {
|
|
|
1109
1198
|
return detected;
|
|
1110
1199
|
});
|
|
1111
1200
|
}
|
|
1112
|
-
writeWithChunking(uuid, hexData) {
|
|
1113
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1114
|
-
const totalBytes = hexData.length / 2;
|
|
1115
|
-
if (totalBytes <= BLE_PACKET_SIZE) {
|
|
1116
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1117
|
-
yield this.writeOnce(uuid, hexData);
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
for (let offset = 0; offset < hexData.length;) {
|
|
1121
|
-
const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
|
|
1122
|
-
const chunkHex = hexData.substring(offset, offset + chunkHexLen);
|
|
1123
|
-
offset += chunkHexLen;
|
|
1124
|
-
yield this.writeOnce(uuid, chunkHex);
|
|
1125
|
-
if (offset < hexData.length) {
|
|
1126
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
1201
|
writeOnce(uuid, hexData) {
|
|
1132
1202
|
var _a;
|
|
1133
1203
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1135,7 +1205,51 @@ class ElectronBleTransport {
|
|
|
1135
1205
|
if (!nobleBle) {
|
|
1136
1206
|
throw new Error('Noble BLE API not available');
|
|
1137
1207
|
}
|
|
1138
|
-
yield nobleBle.write(uuid, hexData);
|
|
1208
|
+
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
refreshBlePacketCapacity(uuid) {
|
|
1212
|
+
var _a, _b;
|
|
1213
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1214
|
+
const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
|
|
1215
|
+
this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
updateBlePacketCapacity(uuid, mtu) {
|
|
1219
|
+
const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
1220
|
+
if (typeof mtu === 'number') {
|
|
1221
|
+
this.deviceMtus.set(uuid, mtu);
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
this.deviceMtus.delete(uuid);
|
|
1225
|
+
}
|
|
1226
|
+
this.devicePacketCapacities.set(uuid, packetCapacity);
|
|
1227
|
+
}
|
|
1228
|
+
createMtuSubscription(uuid) {
|
|
1229
|
+
var _a, _b;
|
|
1230
|
+
const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
|
|
1231
|
+
if (!onMtuChanged)
|
|
1232
|
+
return undefined;
|
|
1233
|
+
return onMtuChanged(device => {
|
|
1234
|
+
if (device.id === uuid) {
|
|
1235
|
+
this.updateBlePacketCapacity(uuid, device.mtu);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
1240
|
+
var _a;
|
|
1241
|
+
const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
|
|
1242
|
+
const shouldPace = !context.highThroughput;
|
|
1243
|
+
return transport.writeProtocolV2BleFrame({
|
|
1244
|
+
frame,
|
|
1245
|
+
packetCapacity,
|
|
1246
|
+
assertActive: assertCurrentGeneration,
|
|
1247
|
+
signal: context.signal,
|
|
1248
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1249
|
+
initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
|
|
1250
|
+
burstSize: shouldPace ? 1 : undefined,
|
|
1251
|
+
burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
|
|
1252
|
+
writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
|
|
1139
1253
|
});
|
|
1140
1254
|
}
|
|
1141
1255
|
handleNotification(deviceId, hexData) {
|
|
@@ -1146,7 +1260,7 @@ class ElectronBleTransport {
|
|
|
1146
1260
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1147
1261
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
1148
1262
|
}
|
|
1149
|
-
else if (this.runPromise) {
|
|
1263
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1150
1264
|
this.runPromise.reject(error);
|
|
1151
1265
|
}
|
|
1152
1266
|
return;
|
|
@@ -1198,16 +1312,8 @@ class ElectronBleTransport {
|
|
|
1198
1312
|
}
|
|
1199
1313
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1200
1314
|
}
|
|
1201
|
-
rejectAllProtocolV2Frames(error) {
|
|
1202
|
-
this.v2FrameQueues.clear();
|
|
1203
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
1204
|
-
framePromise.reject(error);
|
|
1205
|
-
}
|
|
1206
|
-
this.v2FramePromises.clear();
|
|
1207
|
-
}
|
|
1208
1315
|
resetProtocolV2Frames(uuid) {
|
|
1209
|
-
this.
|
|
1210
|
-
this.v2FramePromises.delete(uuid);
|
|
1316
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1211
1317
|
}
|
|
1212
1318
|
rejectProtocolV2Frames(uuid, error) {
|
|
1213
1319
|
this.v2FrameQueues.delete(uuid);
|
|
@@ -1240,17 +1346,19 @@ class ElectronBleTransport {
|
|
|
1240
1346
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1241
1347
|
if (result.error) {
|
|
1242
1348
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1243
|
-
if (this.runPromise) {
|
|
1349
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1244
1350
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1245
1351
|
}
|
|
1246
1352
|
return;
|
|
1247
1353
|
}
|
|
1248
|
-
if (result.isComplete &&
|
|
1354
|
+
if (result.isComplete &&
|
|
1355
|
+
result.completePacket &&
|
|
1356
|
+
this.runPromise &&
|
|
1357
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1249
1358
|
this.runPromise.resolve(result.completePacket);
|
|
1250
1359
|
}
|
|
1251
1360
|
}
|
|
1252
1361
|
call(uuid, name, data, options) {
|
|
1253
|
-
var _a;
|
|
1254
1362
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1255
1363
|
if (!this._messages) {
|
|
1256
1364
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1262,9 +1370,6 @@ class ElectronBleTransport {
|
|
|
1262
1370
|
if (!protocol) {
|
|
1263
1371
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1264
1372
|
}
|
|
1265
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
1266
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
|
|
1267
|
-
}
|
|
1268
1373
|
if (protocol === 'V2') {
|
|
1269
1374
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1270
1375
|
}
|
|
@@ -1284,6 +1389,7 @@ class ElectronBleTransport {
|
|
|
1284
1389
|
const runPromise = hdShared.createDeferred();
|
|
1285
1390
|
runPromise.promise.catch(() => undefined);
|
|
1286
1391
|
this.runPromise = runPromise;
|
|
1392
|
+
this.runPromiseDeviceId = uuid;
|
|
1287
1393
|
const messages = this._messages;
|
|
1288
1394
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1289
1395
|
let timeout;
|
|
@@ -1322,6 +1428,17 @@ class ElectronBleTransport {
|
|
|
1322
1428
|
}
|
|
1323
1429
|
catch (e) {
|
|
1324
1430
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1431
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1432
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1433
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1434
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1435
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1436
|
+
this.notificationCleanups.delete(uuid);
|
|
1437
|
+
this.notificationTokens.delete(uuid);
|
|
1438
|
+
if (!isProbeTimeout) {
|
|
1439
|
+
yield this.releaseNative(uuid);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1325
1442
|
throw e;
|
|
1326
1443
|
}
|
|
1327
1444
|
finally {
|
|
@@ -1329,22 +1446,23 @@ class ElectronBleTransport {
|
|
|
1329
1446
|
clearTimeout(timeout);
|
|
1330
1447
|
if (this.runPromise === runPromise) {
|
|
1331
1448
|
this.runPromise = null;
|
|
1449
|
+
this.runPromiseDeviceId = null;
|
|
1332
1450
|
}
|
|
1333
1451
|
}
|
|
1334
1452
|
});
|
|
1335
1453
|
}
|
|
1336
1454
|
callProtocolV2(uuid, name, data, options) {
|
|
1337
|
-
var _a
|
|
1455
|
+
var _a;
|
|
1338
1456
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1339
1457
|
if (!this._messages || !this._messagesV2) {
|
|
1340
1458
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1341
1459
|
}
|
|
1342
|
-
const callOptions =
|
|
1460
|
+
const callOptions = options;
|
|
1343
1461
|
try {
|
|
1344
1462
|
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
1345
1463
|
}
|
|
1346
1464
|
catch (e) {
|
|
1347
|
-
(
|
|
1465
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1348
1466
|
throw e;
|
|
1349
1467
|
}
|
|
1350
1468
|
});
|
|
@@ -1359,6 +1477,7 @@ class ElectronBleTransport {
|
|
|
1359
1477
|
};
|
|
1360
1478
|
return {
|
|
1361
1479
|
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1480
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1362
1481
|
generation,
|
|
1363
1482
|
prepareCall: () => {
|
|
1364
1483
|
var _a;
|
|
@@ -1366,10 +1485,7 @@ class ElectronBleTransport {
|
|
|
1366
1485
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1367
1486
|
this.resetProtocolV2Frames(uuid);
|
|
1368
1487
|
},
|
|
1369
|
-
writeFrame: (frame) =>
|
|
1370
|
-
assertCurrentGeneration();
|
|
1371
|
-
return this.writeWithChunking(uuid, transport.bytesToHex(frame));
|
|
1372
|
-
},
|
|
1488
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
1373
1489
|
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1374
1490
|
assertCurrentGeneration();
|
|
1375
1491
|
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
@@ -1389,6 +1505,7 @@ class ElectronBleTransport {
|
|
|
1389
1505
|
};
|
|
1390
1506
|
}
|
|
1391
1507
|
processProtocolV1Notification(deviceId, hexData) {
|
|
1508
|
+
var _a;
|
|
1392
1509
|
try {
|
|
1393
1510
|
if (typeof hexData !== 'string') {
|
|
1394
1511
|
return { isComplete: false, error: 'Invalid hexData type' };
|
|
@@ -1407,6 +1524,10 @@ class ElectronBleTransport {
|
|
|
1407
1524
|
bufferState.buffer = [...data.subarray(3)];
|
|
1408
1525
|
}
|
|
1409
1526
|
else {
|
|
1527
|
+
if (bufferState.buffer.length === 0) {
|
|
1528
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
|
|
1529
|
+
return { isComplete: false };
|
|
1530
|
+
}
|
|
1410
1531
|
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
1411
1532
|
}
|
|
1412
1533
|
if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {
|