@onekeyfe/hd-transport-web-device 1.2.0-alpha.1 → 1.2.0-alpha.100
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 +465 -20
- package/__tests__/webusb-protocol-v2-timeout.test.ts +385 -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 +19 -7
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +114 -16
- package/dist/index.js +439 -353
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/webusb.d.ts +16 -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 +321 -196
- package/src/transportLog.ts +1 -0
- package/src/webusb.ts +198 -227
package/dist/index.js
CHANGED
|
@@ -51,33 +51,22 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
|
51
51
|
const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
52
52
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
53
53
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
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
|
-
}
|
|
54
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
56
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
68
57
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
69
58
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
70
59
|
}
|
|
71
|
-
class WebUsbTransport {
|
|
60
|
+
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
72
61
|
constructor() {
|
|
62
|
+
super({
|
|
63
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
64
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
65
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
66
|
+
});
|
|
73
67
|
this.deviceProtocol = new Map();
|
|
74
68
|
this.deviceProtocolHints = new Map();
|
|
75
|
-
this.protocolV2Assemblers = new Map();
|
|
76
|
-
this.protocolV2Sessions = new Map();
|
|
77
|
-
this.protocolV2ReadTimeouts = new Map();
|
|
78
69
|
this.deviceEndpoints = new Map();
|
|
79
|
-
this.mockSerialPaths = new WeakMap();
|
|
80
|
-
this.mockSerialCounter = 0;
|
|
81
70
|
this.name = 'WebUsbTransport';
|
|
82
71
|
this.stopped = false;
|
|
83
72
|
this.configured = false;
|
|
@@ -101,10 +90,17 @@ class WebUsbTransport {
|
|
|
101
90
|
}
|
|
102
91
|
configureProtocolV2(signedData) {
|
|
103
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;
|
|
104
99
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
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); });
|
|
108
104
|
}
|
|
109
105
|
promptDeviceAccess() {
|
|
110
106
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -126,28 +122,18 @@ class WebUsbTransport {
|
|
|
126
122
|
return this.deviceList;
|
|
127
123
|
});
|
|
128
124
|
}
|
|
129
|
-
getDevicePath(device) {
|
|
130
|
-
var _a;
|
|
131
|
-
if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
|
|
132
|
-
return device.serialNumber;
|
|
133
|
-
}
|
|
134
|
-
let path = this.mockSerialPaths.get(device);
|
|
135
|
-
if (!path) {
|
|
136
|
-
this.mockSerialCounter += 1;
|
|
137
|
-
path = `mock-serial:${device.vendorId.toString(16)}:${device.productId.toString(16)}:${this.mockSerialCounter}`;
|
|
138
|
-
this.mockSerialPaths.set(device, path);
|
|
139
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUSB] device has no serial number, using mock path: ${path}`);
|
|
140
|
-
}
|
|
141
|
-
return path;
|
|
142
|
-
}
|
|
143
125
|
getConnectedDevices() {
|
|
144
126
|
return __awaiter(this, void 0, void 0, function* () {
|
|
145
127
|
if (!this.usb)
|
|
146
128
|
return [];
|
|
147
129
|
const devices = yield this.usb.getDevices();
|
|
148
|
-
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
|
+
});
|
|
149
135
|
this.deviceList = onekeyDevices.map(device => {
|
|
150
|
-
const path =
|
|
136
|
+
const path = device.serialNumber;
|
|
151
137
|
const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
|
|
152
138
|
if (protocolHint) {
|
|
153
139
|
this.deviceProtocolHints.set(path, protocolHint);
|
|
@@ -158,25 +144,22 @@ class WebUsbTransport {
|
|
|
158
144
|
commType: 'webusb',
|
|
159
145
|
};
|
|
160
146
|
});
|
|
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
147
|
return this.deviceList;
|
|
166
148
|
});
|
|
167
149
|
}
|
|
168
150
|
acquire(input) {
|
|
169
|
-
var _a, _b, _c;
|
|
151
|
+
var _a, _b, _c, _d;
|
|
170
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
171
153
|
if (!input.path)
|
|
172
154
|
return;
|
|
173
155
|
try {
|
|
156
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
174
157
|
yield this.closeOpenDevice(input.path);
|
|
175
158
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
176
159
|
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
177
160
|
const protocolHint = input.expectedProtocol
|
|
178
161
|
? undefined
|
|
179
|
-
: (_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);
|
|
180
163
|
if (protocolHint) {
|
|
181
164
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
182
165
|
}
|
|
@@ -185,6 +168,7 @@ class WebUsbTransport {
|
|
|
185
168
|
}
|
|
186
169
|
catch (e) {
|
|
187
170
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
171
|
+
yield this.closeOpenDevice(input.path);
|
|
188
172
|
throw e;
|
|
189
173
|
}
|
|
190
174
|
});
|
|
@@ -192,32 +176,53 @@ class WebUsbTransport {
|
|
|
192
176
|
createProtocolMismatchError(expected) {
|
|
193
177
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
194
178
|
}
|
|
179
|
+
createProtocolProbeTimeoutError(expected, attempts) {
|
|
180
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
181
|
+
}
|
|
195
182
|
createProtocolDetectionError() {
|
|
196
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
183
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
197
184
|
}
|
|
198
185
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
186
|
+
var _a;
|
|
199
187
|
return __awaiter(this, void 0, void 0, function* () {
|
|
200
188
|
if (expectedProtocol === 'V1') {
|
|
201
189
|
if (yield this.probeProtocolV1(path)) {
|
|
202
190
|
this.deviceProtocol.set(path, 'V1');
|
|
203
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
204
191
|
return 'V1';
|
|
205
192
|
}
|
|
193
|
+
yield this.closeConnectionAfterProbe(path);
|
|
206
194
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
207
195
|
}
|
|
208
196
|
if (expectedProtocol === 'V2') {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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);
|
|
212
212
|
}
|
|
213
213
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
214
|
-
for (const protocol of probeOrder) {
|
|
214
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
215
215
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
216
216
|
if (detected) {
|
|
217
217
|
this.deviceProtocol.set(path, protocol);
|
|
218
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
219
218
|
return protocol;
|
|
220
219
|
}
|
|
220
|
+
if (index < probeOrder.length - 1) {
|
|
221
|
+
yield this.resetConnectionAfterProbe(path);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
yield this.closeConnectionAfterProbe(path);
|
|
225
|
+
}
|
|
221
226
|
}
|
|
222
227
|
this.deviceProtocol.delete(path);
|
|
223
228
|
throw this.createProtocolDetectionError();
|
|
@@ -284,10 +289,9 @@ class WebUsbTransport {
|
|
|
284
289
|
};
|
|
285
290
|
}
|
|
286
291
|
connectToDevice(path, first) {
|
|
287
|
-
var _a
|
|
292
|
+
var _a;
|
|
288
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
289
294
|
let device = yield this.findDevice(path);
|
|
290
|
-
this.Log.debug('[WebUsbTransport] connecting to device:', device.productName, 'PID:', device.productId);
|
|
291
295
|
if (!device.opened) {
|
|
292
296
|
yield device.open();
|
|
293
297
|
}
|
|
@@ -309,33 +313,30 @@ class WebUsbTransport {
|
|
|
309
313
|
}
|
|
310
314
|
const endpoints = this.discoverEndpoints(device);
|
|
311
315
|
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
316
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
315
317
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
316
318
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
317
319
|
});
|
|
318
320
|
}
|
|
319
321
|
closeOpenDevice(path) {
|
|
320
|
-
var _a, _b, _c, _d
|
|
322
|
+
var _a, _b, _c, _d;
|
|
321
323
|
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;
|
|
324
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
324
325
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
325
326
|
return;
|
|
326
327
|
const endpoints = this.deviceEndpoints.get(path);
|
|
327
|
-
const ifaceNum = (
|
|
328
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
328
329
|
try {
|
|
329
330
|
yield current.releaseInterface(ifaceNum);
|
|
330
331
|
}
|
|
331
332
|
catch (error) {
|
|
332
|
-
(
|
|
333
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
333
334
|
}
|
|
334
335
|
try {
|
|
335
336
|
yield current.close();
|
|
336
337
|
}
|
|
337
338
|
catch (error) {
|
|
338
|
-
(
|
|
339
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
339
340
|
}
|
|
340
341
|
});
|
|
341
342
|
}
|
|
@@ -420,19 +421,11 @@ class WebUsbTransport {
|
|
|
420
421
|
return copied.buffer;
|
|
421
422
|
}
|
|
422
423
|
transferOutWithRetry(path, packet) {
|
|
423
|
-
var _a;
|
|
424
424
|
return __awaiter(this, void 0, void 0, function* () {
|
|
425
425
|
let lastError;
|
|
426
426
|
for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
|
|
427
427
|
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);
|
|
428
|
+
yield this.transferOutOnce(path, packet);
|
|
436
429
|
return;
|
|
437
430
|
}
|
|
438
431
|
catch (error) {
|
|
@@ -453,6 +446,19 @@ class WebUsbTransport {
|
|
|
453
446
|
throw lastError;
|
|
454
447
|
});
|
|
455
448
|
}
|
|
449
|
+
transferOutOnce(path, packet) {
|
|
450
|
+
var _a;
|
|
451
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
452
|
+
const device = yield this.findDevice(path);
|
|
453
|
+
if (!device.opened) {
|
|
454
|
+
yield this.connect(path, false);
|
|
455
|
+
}
|
|
456
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
457
|
+
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
458
|
+
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
459
|
+
yield device.transferOut(endpointOut, transferBuffer);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
456
462
|
transferInWithRetry(path, length, cancelToken) {
|
|
457
463
|
var _a;
|
|
458
464
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -492,29 +498,28 @@ class WebUsbTransport {
|
|
|
492
498
|
throw lastError;
|
|
493
499
|
});
|
|
494
500
|
}
|
|
495
|
-
|
|
496
|
-
var _a
|
|
501
|
+
transferInOnce(path, length) {
|
|
502
|
+
var _a;
|
|
497
503
|
return __awaiter(this, void 0, void 0, function* () {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
const device = yield this.findDevice(path);
|
|
503
|
-
if (device.opened) {
|
|
504
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
505
|
-
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
506
|
-
try {
|
|
507
|
-
yield device.releaseInterface(ifaceNum);
|
|
508
|
-
}
|
|
509
|
-
catch (error) {
|
|
510
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
511
|
-
}
|
|
512
|
-
yield device.close();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
catch (error) {
|
|
516
|
-
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');
|
|
517
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);
|
|
518
523
|
yield this.getConnectedDevices();
|
|
519
524
|
yield this.connect(path, false);
|
|
520
525
|
});
|
|
@@ -537,6 +542,14 @@ class WebUsbTransport {
|
|
|
537
542
|
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
538
543
|
timedOut = true;
|
|
539
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
|
+
}
|
|
540
553
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
541
554
|
}), timeoutMs);
|
|
542
555
|
}),
|
|
@@ -554,11 +567,12 @@ class WebUsbTransport {
|
|
|
554
567
|
return false;
|
|
555
568
|
}
|
|
556
569
|
try {
|
|
557
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
570
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
571
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
572
|
+
});
|
|
558
573
|
return true;
|
|
559
574
|
}
|
|
560
|
-
catch (
|
|
561
|
-
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
575
|
+
catch (_error) {
|
|
562
576
|
return false;
|
|
563
577
|
}
|
|
564
578
|
});
|
|
@@ -570,10 +584,9 @@ class WebUsbTransport {
|
|
|
570
584
|
}
|
|
571
585
|
return transport.probeProtocolV2({
|
|
572
586
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
573
|
-
timeoutMs:
|
|
587
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
574
588
|
logger: this.Log,
|
|
575
589
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
576
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
577
590
|
});
|
|
578
591
|
});
|
|
579
592
|
}
|
|
@@ -590,12 +603,8 @@ class WebUsbTransport {
|
|
|
590
603
|
if (!protocol) {
|
|
591
604
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
592
605
|
}
|
|
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);
|
|
606
|
+
if (!transport.shouldSuppressHighVolumeCallLog(name)) {
|
|
607
|
+
this.Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
|
|
599
608
|
}
|
|
600
609
|
if (protocol === 'V2') {
|
|
601
610
|
return this.callProtocolV2(path, name, data, options);
|
|
@@ -625,67 +634,8 @@ class WebUsbTransport {
|
|
|
625
634
|
});
|
|
626
635
|
}
|
|
627
636
|
callProtocolV2(path, name, data, options) {
|
|
628
|
-
var _a;
|
|
629
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
630
|
-
const protocolV1Messages = this.messages;
|
|
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
637
|
return __awaiter(this, void 0, void 0, function* () {
|
|
665
|
-
|
|
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;
|
|
638
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
689
639
|
});
|
|
690
640
|
}
|
|
691
641
|
receiveData(path, timeoutMs) {
|
|
@@ -726,44 +676,71 @@ class WebUsbTransport {
|
|
|
726
676
|
});
|
|
727
677
|
}
|
|
728
678
|
release(path) {
|
|
729
|
-
var _a, _b;
|
|
730
679
|
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();
|
|
680
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
681
|
+
yield this.closeOpenDevice(path);
|
|
736
682
|
this.deviceProtocol.delete(path);
|
|
737
683
|
this.deviceProtocolHints.delete(path);
|
|
738
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
739
|
-
this.protocolV2Assemblers.delete(path);
|
|
740
684
|
this.deviceEndpoints.delete(path);
|
|
741
685
|
});
|
|
742
686
|
}
|
|
687
|
+
getProtocolV2UsbSchemas() {
|
|
688
|
+
if (!this.messages || !this.messagesV2) {
|
|
689
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
protocolV1: this.messages,
|
|
693
|
+
protocolV2: this.messagesV2,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
getProtocolV2UsbLogger() {
|
|
697
|
+
return this.Log;
|
|
698
|
+
}
|
|
699
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
700
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
701
|
+
yield this.transferOutOnce(path, frame);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
705
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
706
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
707
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
711
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
712
|
+
yield this.closeOpenDevice(path);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
716
|
+
var _a;
|
|
717
|
+
this.deviceProtocol.delete(path);
|
|
718
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
719
|
+
}
|
|
720
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
721
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
722
|
+
}
|
|
743
723
|
getProtocolType(path) {
|
|
744
724
|
return this.deviceProtocol.get(path);
|
|
745
725
|
}
|
|
746
726
|
}
|
|
747
727
|
|
|
748
|
-
const
|
|
749
|
-
function
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
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;
|
|
728
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
729
|
+
function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
|
|
730
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
731
|
+
return fallbackPacketCapacity;
|
|
732
|
+
}
|
|
733
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
756
734
|
}
|
|
735
|
+
|
|
757
736
|
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
758
737
|
function inferProtocolHintFromDeviceName(name) {
|
|
759
738
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
760
739
|
}
|
|
761
740
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
762
|
-
const
|
|
741
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
742
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
763
743
|
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
744
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
768
745
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
769
746
|
class ElectronBleTransport {
|
|
@@ -771,16 +748,40 @@ class ElectronBleTransport {
|
|
|
771
748
|
this.name = 'ElectronBleTransport';
|
|
772
749
|
this.configured = false;
|
|
773
750
|
this.runPromise = null;
|
|
751
|
+
this.runPromiseDeviceId = null;
|
|
774
752
|
this.connectedDevices = new Set();
|
|
775
753
|
this.deviceProtocol = new Map();
|
|
776
754
|
this.deviceProtocolHints = new Map();
|
|
755
|
+
this.deviceMtus = new Map();
|
|
756
|
+
this.devicePacketCapacities = new Map();
|
|
777
757
|
this.v1Buffers = new Map();
|
|
778
758
|
this.v2Assemblers = new Map();
|
|
779
759
|
this.v2FrameQueues = new Map();
|
|
780
760
|
this.v2FramePromises = new Map();
|
|
781
|
-
this.
|
|
782
|
-
|
|
761
|
+
this.protocolV2Links = new transport.ProtocolV2LinkManager({
|
|
762
|
+
getSchemas: () => {
|
|
763
|
+
if (!this._messages || !this._messagesV2) {
|
|
764
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
765
|
+
}
|
|
766
|
+
return {
|
|
767
|
+
protocolV1: this._messages,
|
|
768
|
+
protocolV2: this._messagesV2,
|
|
769
|
+
};
|
|
770
|
+
},
|
|
771
|
+
classifyError: () => 'link-fatal',
|
|
772
|
+
onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
773
|
+
var _a, _b;
|
|
774
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
775
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
776
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
777
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
778
|
+
yield this.releaseNative(uuid);
|
|
779
|
+
}
|
|
780
|
+
}),
|
|
781
|
+
});
|
|
782
|
+
this.warnedMissingRelease = false;
|
|
783
783
|
this.notificationCleanups = new Map();
|
|
784
|
+
this.mtuCleanups = new Map();
|
|
784
785
|
this.disconnectCleanups = new Map();
|
|
785
786
|
this.notificationTokens = new Map();
|
|
786
787
|
this.nextNotificationToken = 1;
|
|
@@ -815,21 +816,32 @@ class ElectronBleTransport {
|
|
|
815
816
|
throw error;
|
|
816
817
|
}
|
|
817
818
|
cleanupDeviceState(deviceId) {
|
|
818
|
-
|
|
819
|
+
this.protocolV2Links
|
|
820
|
+
.invalidateLink(deviceId, 'Electron BLE device state cleaned')
|
|
821
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
|
|
819
822
|
this.connectedDevices.delete(deviceId);
|
|
820
823
|
this.deviceProtocol.delete(deviceId);
|
|
824
|
+
this.deviceMtus.delete(deviceId);
|
|
825
|
+
this.devicePacketCapacities.delete(deviceId);
|
|
821
826
|
this.v1Buffers.delete(deviceId);
|
|
822
827
|
this.v2Assemblers.delete(deviceId);
|
|
823
828
|
this.resetProtocolV2Frames(deviceId);
|
|
824
|
-
if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
|
|
825
|
-
this.activeProtocolV2Call = null;
|
|
826
|
-
}
|
|
827
829
|
this.notificationTokens.delete(deviceId);
|
|
830
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
831
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
|
|
832
|
+
this.runPromise = null;
|
|
833
|
+
this.runPromiseDeviceId = null;
|
|
834
|
+
}
|
|
828
835
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
829
836
|
if (notifyCleanup) {
|
|
830
837
|
notifyCleanup();
|
|
831
838
|
this.notificationCleanups.delete(deviceId);
|
|
832
839
|
}
|
|
840
|
+
const mtuCleanup = this.mtuCleanups.get(deviceId);
|
|
841
|
+
if (mtuCleanup) {
|
|
842
|
+
mtuCleanup();
|
|
843
|
+
this.mtuCleanups.delete(deviceId);
|
|
844
|
+
}
|
|
833
845
|
const disconnectCleanup = this.disconnectCleanups.get(deviceId);
|
|
834
846
|
if (disconnectCleanup) {
|
|
835
847
|
disconnectCleanup();
|
|
@@ -850,9 +862,18 @@ class ElectronBleTransport {
|
|
|
850
862
|
this.configured = true;
|
|
851
863
|
}
|
|
852
864
|
configureProtocolV2(signedData) {
|
|
853
|
-
|
|
865
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
866
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
854
870
|
this._messagesV2 = parseConfigure(signedData);
|
|
855
|
-
|
|
871
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
872
|
+
if (isReconfiguration) {
|
|
873
|
+
this.protocolV2Links
|
|
874
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
875
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
|
|
876
|
+
}
|
|
856
877
|
}
|
|
857
878
|
listen() {
|
|
858
879
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -884,18 +905,20 @@ class ElectronBleTransport {
|
|
|
884
905
|
});
|
|
885
906
|
}
|
|
886
907
|
acquire(input) {
|
|
887
|
-
var _a, _b, _c, _d, _e, _f;
|
|
908
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
888
909
|
return __awaiter(this, void 0, void 0, function* () {
|
|
889
910
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
890
911
|
if (!uuid) {
|
|
891
912
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
892
913
|
}
|
|
914
|
+
if (this.connectedDevices.has(uuid)) {
|
|
915
|
+
yield this.release(uuid);
|
|
916
|
+
}
|
|
893
917
|
if (forceCleanRunPromise && this.runPromise) {
|
|
894
918
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
895
919
|
this.runPromise.reject(error);
|
|
896
|
-
this.rejectAllProtocolV2Frames(error);
|
|
897
920
|
this.runPromise = null;
|
|
898
|
-
this.
|
|
921
|
+
this.runPromiseDeviceId = null;
|
|
899
922
|
}
|
|
900
923
|
try {
|
|
901
924
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -907,7 +930,7 @@ class ElectronBleTransport {
|
|
|
907
930
|
}
|
|
908
931
|
const protocolHint = expectedProtocol
|
|
909
932
|
? undefined
|
|
910
|
-
: (_b = this.deviceProtocolHints.get(uuid)) !== null &&
|
|
933
|
+
: (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
|
|
911
934
|
if (protocolHint) {
|
|
912
935
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
913
936
|
}
|
|
@@ -918,16 +941,31 @@ class ElectronBleTransport {
|
|
|
918
941
|
catch (error) {
|
|
919
942
|
this.handleBluetoothError(error);
|
|
920
943
|
}
|
|
944
|
+
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
945
|
+
if (mtuCleanup) {
|
|
946
|
+
this.mtuCleanups.set(uuid, mtuCleanup);
|
|
947
|
+
}
|
|
921
948
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
922
|
-
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
|
|
949
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
923
950
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
951
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
924
952
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
925
953
|
this.notificationCleanups.set(uuid, cleanup);
|
|
954
|
+
const connectionToken = this.notificationTokens.get(uuid);
|
|
955
|
+
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
956
|
+
if (protocolType === 'V2') {
|
|
957
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
|
|
958
|
+
writeMode: 'withoutResponse',
|
|
959
|
+
negotiatedMtu: this.deviceMtus.get(uuid),
|
|
960
|
+
packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
926
963
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
927
964
|
var _a;
|
|
928
|
-
if (disconnectedDevice.id === uuid
|
|
965
|
+
if (disconnectedDevice.id === uuid &&
|
|
966
|
+
this.notificationTokens.get(uuid) === connectionToken) {
|
|
929
967
|
this.cleanupDeviceState(uuid);
|
|
930
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(
|
|
968
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
931
969
|
name: disconnectedDevice.name,
|
|
932
970
|
id: disconnectedDevice.id,
|
|
933
971
|
connectId: disconnectedDevice.id,
|
|
@@ -935,31 +973,43 @@ class ElectronBleTransport {
|
|
|
935
973
|
}
|
|
936
974
|
});
|
|
937
975
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
938
|
-
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
976
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
945
977
|
}
|
|
946
978
|
catch (error) {
|
|
947
|
-
(
|
|
979
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
|
|
948
980
|
try {
|
|
949
|
-
if (((
|
|
981
|
+
if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
950
982
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
951
983
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
952
984
|
}
|
|
953
985
|
}
|
|
954
986
|
catch (cleanupError) {
|
|
955
|
-
(
|
|
987
|
+
(_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
956
988
|
}
|
|
957
989
|
this.cleanupDeviceState(uuid);
|
|
958
990
|
throw error;
|
|
959
991
|
}
|
|
960
992
|
});
|
|
961
993
|
}
|
|
962
|
-
release(id) {
|
|
994
|
+
release(id, _onclose, keepSession) {
|
|
995
|
+
var _a;
|
|
996
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
997
|
+
try {
|
|
998
|
+
yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
999
|
+
yield this.releaseLogical(id, keepSession);
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
|
|
1003
|
+
this.cleanupDeviceState(id);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
disconnect(id) {
|
|
1008
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1009
|
+
return this.releaseNative(id);
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
releaseNative(id) {
|
|
963
1013
|
var _a, _b;
|
|
964
1014
|
return __awaiter(this, void 0, void 0, function* () {
|
|
965
1015
|
try {
|
|
@@ -977,11 +1027,35 @@ class ElectronBleTransport {
|
|
|
977
1027
|
}
|
|
978
1028
|
});
|
|
979
1029
|
}
|
|
1030
|
+
releaseLogical(id, keepSession) {
|
|
1031
|
+
var _a, _b, _c, _d;
|
|
1032
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1033
|
+
try {
|
|
1034
|
+
if (!this.connectedDevices.has(id))
|
|
1035
|
+
return;
|
|
1036
|
+
this.cleanupDeviceState(id);
|
|
1037
|
+
const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
|
|
1038
|
+
if (!release) {
|
|
1039
|
+
if (!this.warnedMissingRelease) {
|
|
1040
|
+
this.warnedMissingRelease = true;
|
|
1041
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
|
|
1042
|
+
'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
|
|
1043
|
+
}
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
yield release(id, keepSession);
|
|
1047
|
+
}
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
|
|
1050
|
+
this.cleanupDeviceState(id);
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
980
1054
|
createProtocolMismatchError(expected) {
|
|
981
1055
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
982
1056
|
}
|
|
983
1057
|
createProtocolDetectionError() {
|
|
984
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1058
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
985
1059
|
}
|
|
986
1060
|
clearProbeProtocol(uuid, protocol) {
|
|
987
1061
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1000,9 +1074,12 @@ class ElectronBleTransport {
|
|
|
1000
1074
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1001
1075
|
}
|
|
1002
1076
|
if (expectedProtocol === 'V2') {
|
|
1003
|
-
this.
|
|
1004
|
-
|
|
1005
|
-
|
|
1077
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
1078
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1079
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1080
|
+
return 'V2';
|
|
1081
|
+
}
|
|
1082
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1006
1083
|
}
|
|
1007
1084
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1008
1085
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1036,18 +1113,17 @@ class ElectronBleTransport {
|
|
|
1036
1113
|
});
|
|
1037
1114
|
}
|
|
1038
1115
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1039
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1116
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1040
1117
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1118
|
+
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1041
1119
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1042
1120
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1043
1121
|
this.resetProtocolV2Frames(uuid);
|
|
1044
|
-
if (
|
|
1045
|
-
this.activeProtocolV2Call = null;
|
|
1046
|
-
}
|
|
1047
|
-
if (this.runPromise) {
|
|
1122
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1048
1123
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1049
1124
|
this.runPromise.reject(error);
|
|
1050
1125
|
this.runPromise = null;
|
|
1126
|
+
this.runPromiseDeviceId = null;
|
|
1051
1127
|
}
|
|
1052
1128
|
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1053
1129
|
if (notifyCleanup) {
|
|
@@ -1056,16 +1132,26 @@ class ElectronBleTransport {
|
|
|
1056
1132
|
}
|
|
1057
1133
|
this.notificationTokens.delete(uuid);
|
|
1058
1134
|
try {
|
|
1059
|
-
yield ((
|
|
1135
|
+
yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
|
|
1136
|
+
}
|
|
1137
|
+
catch (error) {
|
|
1138
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
|
|
1060
1142
|
}
|
|
1061
1143
|
catch (error) {
|
|
1062
|
-
(
|
|
1144
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
|
|
1063
1145
|
}
|
|
1146
|
+
this.connectedDevices.delete(uuid);
|
|
1064
1147
|
try {
|
|
1065
|
-
yield ((
|
|
1148
|
+
yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
|
|
1149
|
+
this.connectedDevices.add(uuid);
|
|
1150
|
+
yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
|
|
1151
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
1066
1152
|
}
|
|
1067
1153
|
catch (error) {
|
|
1068
|
-
(
|
|
1154
|
+
(_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
|
|
1069
1155
|
throw error;
|
|
1070
1156
|
}
|
|
1071
1157
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
@@ -1080,12 +1166,12 @@ class ElectronBleTransport {
|
|
|
1080
1166
|
}
|
|
1081
1167
|
try {
|
|
1082
1168
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1083
|
-
yield this.callProtocolV1(uuid, '
|
|
1169
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1084
1170
|
return true;
|
|
1085
1171
|
}
|
|
1086
1172
|
catch (error) {
|
|
1087
1173
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1088
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1174
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1089
1175
|
return false;
|
|
1090
1176
|
}
|
|
1091
1177
|
});
|
|
@@ -1115,57 +1201,70 @@ class ElectronBleTransport {
|
|
|
1115
1201
|
return detected;
|
|
1116
1202
|
});
|
|
1117
1203
|
}
|
|
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;
|
|
1204
|
+
writeOnce(uuid, hexData) {
|
|
1205
|
+
var _a;
|
|
1139
1206
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1140
|
-
let lastError;
|
|
1141
1207
|
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1142
1208
|
if (!nobleBle) {
|
|
1143
1209
|
throw new Error('Noble BLE API not available');
|
|
1144
1210
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1211
|
+
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
refreshBlePacketCapacity(uuid) {
|
|
1215
|
+
var _a, _b;
|
|
1216
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1217
|
+
const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
|
|
1218
|
+
this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
updateBlePacketCapacity(uuid, mtu) {
|
|
1222
|
+
const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
1223
|
+
if (typeof mtu === 'number') {
|
|
1224
|
+
this.deviceMtus.set(uuid, mtu);
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
this.deviceMtus.delete(uuid);
|
|
1228
|
+
}
|
|
1229
|
+
this.devicePacketCapacities.set(uuid, packetCapacity);
|
|
1230
|
+
}
|
|
1231
|
+
createMtuSubscription(uuid) {
|
|
1232
|
+
var _a, _b;
|
|
1233
|
+
const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
|
|
1234
|
+
if (!onMtuChanged)
|
|
1235
|
+
return undefined;
|
|
1236
|
+
return onMtuChanged(device => {
|
|
1237
|
+
if (device.id === uuid) {
|
|
1238
|
+
this.updateBlePacketCapacity(uuid, device.mtu);
|
|
1157
1239
|
}
|
|
1158
|
-
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
1243
|
+
var _a;
|
|
1244
|
+
const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
|
|
1245
|
+
const shouldPace = !context.highThroughput;
|
|
1246
|
+
return transport.writeProtocolV2BleFrame({
|
|
1247
|
+
frame,
|
|
1248
|
+
packetCapacity,
|
|
1249
|
+
assertActive: assertCurrentGeneration,
|
|
1250
|
+
signal: context.signal,
|
|
1251
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1252
|
+
initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
|
|
1253
|
+
burstSize: shouldPace ? 1 : undefined,
|
|
1254
|
+
burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
|
|
1255
|
+
writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
|
|
1159
1256
|
});
|
|
1160
1257
|
}
|
|
1161
1258
|
handleNotification(deviceId, hexData) {
|
|
1162
1259
|
var _a, _b;
|
|
1163
1260
|
if (hexData === 'PAIRING_REJECTED') {
|
|
1164
1261
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
|
|
1165
|
-
|
|
1166
|
-
|
|
1262
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
|
|
1263
|
+
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1264
|
+
this.rejectProtocolV2Frames(deviceId, error);
|
|
1265
|
+
}
|
|
1266
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1167
1267
|
this.runPromise.reject(error);
|
|
1168
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1169
1268
|
}
|
|
1170
1269
|
return;
|
|
1171
1270
|
}
|
|
@@ -1181,13 +1280,8 @@ class ElectronBleTransport {
|
|
|
1181
1280
|
this.handleProtocolV1Notification(deviceId, hexData);
|
|
1182
1281
|
}
|
|
1183
1282
|
handleProtocolV2Notification(deviceId, hexData) {
|
|
1184
|
-
var _a
|
|
1283
|
+
var _a;
|
|
1185
1284
|
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
1285
|
const bytes = transport.hexToBytes(hexData);
|
|
1192
1286
|
if (bytes.length === 0)
|
|
1193
1287
|
return;
|
|
@@ -1199,12 +1293,9 @@ class ElectronBleTransport {
|
|
|
1199
1293
|
}
|
|
1200
1294
|
}
|
|
1201
1295
|
catch (error) {
|
|
1202
|
-
(
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
this.runPromise.reject(notifyError);
|
|
1206
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
1207
|
-
}
|
|
1296
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 notification error:', error);
|
|
1297
|
+
const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
1298
|
+
this.rejectProtocolV2Frames(deviceId, notifyError);
|
|
1208
1299
|
}
|
|
1209
1300
|
}
|
|
1210
1301
|
getProtocolV2FrameQueue(uuid) {
|
|
@@ -1224,20 +1315,16 @@ class ElectronBleTransport {
|
|
|
1224
1315
|
}
|
|
1225
1316
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1226
1317
|
}
|
|
1227
|
-
rejectAllProtocolV2Frames(error) {
|
|
1228
|
-
this.v2FrameQueues.clear();
|
|
1229
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
1230
|
-
framePromise.reject(error);
|
|
1231
|
-
}
|
|
1232
|
-
this.v2FramePromises.clear();
|
|
1233
|
-
}
|
|
1234
1318
|
resetProtocolV2Frames(uuid) {
|
|
1235
|
-
this.
|
|
1236
|
-
this.v2FramePromises.delete(uuid);
|
|
1319
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1237
1320
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1321
|
+
rejectProtocolV2Frames(uuid, error) {
|
|
1322
|
+
this.v2FrameQueues.delete(uuid);
|
|
1323
|
+
const framePromise = this.v2FramePromises.get(uuid);
|
|
1324
|
+
if (framePromise) {
|
|
1325
|
+
this.v2FramePromises.delete(uuid);
|
|
1326
|
+
framePromise.reject(error);
|
|
1327
|
+
}
|
|
1241
1328
|
}
|
|
1242
1329
|
readProtocolV2Frame(uuid) {
|
|
1243
1330
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1262,17 +1349,20 @@ class ElectronBleTransport {
|
|
|
1262
1349
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1263
1350
|
if (result.error) {
|
|
1264
1351
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1265
|
-
if (this.runPromise) {
|
|
1352
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1266
1353
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1267
1354
|
}
|
|
1268
1355
|
return;
|
|
1269
1356
|
}
|
|
1270
|
-
if (result.isComplete &&
|
|
1357
|
+
if (result.isComplete &&
|
|
1358
|
+
result.completePacket &&
|
|
1359
|
+
this.runPromise &&
|
|
1360
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1271
1361
|
this.runPromise.resolve(result.completePacket);
|
|
1272
1362
|
}
|
|
1273
1363
|
}
|
|
1274
1364
|
call(uuid, name, data, options) {
|
|
1275
|
-
var _a
|
|
1365
|
+
var _a;
|
|
1276
1366
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1277
1367
|
if (!this._messages) {
|
|
1278
1368
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1284,12 +1374,8 @@ class ElectronBleTransport {
|
|
|
1284
1374
|
if (!protocol) {
|
|
1285
1375
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1286
1376
|
}
|
|
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);
|
|
1377
|
+
if (!transport.shouldSuppressHighVolumeCallLog(name)) {
|
|
1378
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', transport.createTransportCallLog(name, protocol, data));
|
|
1293
1379
|
}
|
|
1294
1380
|
if (protocol === 'V2') {
|
|
1295
1381
|
return this.callProtocolV2(uuid, name, data, options);
|
|
@@ -1310,6 +1396,7 @@ class ElectronBleTransport {
|
|
|
1310
1396
|
const runPromise = hdShared.createDeferred();
|
|
1311
1397
|
runPromise.promise.catch(() => undefined);
|
|
1312
1398
|
this.runPromise = runPromise;
|
|
1399
|
+
this.runPromiseDeviceId = uuid;
|
|
1313
1400
|
const messages = this._messages;
|
|
1314
1401
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1315
1402
|
let timeout;
|
|
@@ -1348,6 +1435,17 @@ class ElectronBleTransport {
|
|
|
1348
1435
|
}
|
|
1349
1436
|
catch (e) {
|
|
1350
1437
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1438
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1439
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1440
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1441
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1442
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1443
|
+
this.notificationCleanups.delete(uuid);
|
|
1444
|
+
this.notificationTokens.delete(uuid);
|
|
1445
|
+
if (!isProbeTimeout) {
|
|
1446
|
+
yield this.releaseNative(uuid);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1351
1449
|
throw e;
|
|
1352
1450
|
}
|
|
1353
1451
|
finally {
|
|
@@ -1355,82 +1453,66 @@ class ElectronBleTransport {
|
|
|
1355
1453
|
clearTimeout(timeout);
|
|
1356
1454
|
if (this.runPromise === runPromise) {
|
|
1357
1455
|
this.runPromise = null;
|
|
1456
|
+
this.runPromiseDeviceId = null;
|
|
1358
1457
|
}
|
|
1359
1458
|
}
|
|
1360
1459
|
});
|
|
1361
1460
|
}
|
|
1362
1461
|
callProtocolV2(uuid, name, data, options) {
|
|
1363
|
-
var _a
|
|
1462
|
+
var _a;
|
|
1364
1463
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1365
1464
|
if (!this._messages || !this._messagesV2) {
|
|
1366
1465
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1367
1466
|
}
|
|
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 });
|
|
1467
|
+
const callOptions = options;
|
|
1388
1468
|
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;
|
|
1469
|
+
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
1410
1470
|
}
|
|
1411
1471
|
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);
|
|
1472
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1417
1473
|
throw e;
|
|
1418
1474
|
}
|
|
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
1475
|
});
|
|
1432
1476
|
}
|
|
1477
|
+
createProtocolV2Adapter(uuid) {
|
|
1478
|
+
var _a;
|
|
1479
|
+
const generation = (_a = this.notificationTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
1480
|
+
const assertCurrentGeneration = () => {
|
|
1481
|
+
if (this.notificationTokens.get(uuid) !== generation) {
|
|
1482
|
+
throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
return {
|
|
1486
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1487
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1488
|
+
generation,
|
|
1489
|
+
prepareCall: () => {
|
|
1490
|
+
var _a;
|
|
1491
|
+
assertCurrentGeneration();
|
|
1492
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1493
|
+
this.resetProtocolV2Frames(uuid);
|
|
1494
|
+
},
|
|
1495
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
1496
|
+
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1497
|
+
assertCurrentGeneration();
|
|
1498
|
+
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
1499
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1500
|
+
throw new Error('Response is not Uint8Array');
|
|
1501
|
+
}
|
|
1502
|
+
return rxFrame;
|
|
1503
|
+
}),
|
|
1504
|
+
reset: (reason) => {
|
|
1505
|
+
var _a;
|
|
1506
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1507
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1508
|
+
},
|
|
1509
|
+
logger: this.Log,
|
|
1510
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
1511
|
+
createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1433
1514
|
processProtocolV1Notification(deviceId, hexData) {
|
|
1515
|
+
var _a;
|
|
1434
1516
|
try {
|
|
1435
1517
|
if (typeof hexData !== 'string') {
|
|
1436
1518
|
return { isComplete: false, error: 'Invalid hexData type' };
|
|
@@ -1449,6 +1531,10 @@ class ElectronBleTransport {
|
|
|
1449
1531
|
bufferState.buffer = [...data.subarray(3)];
|
|
1450
1532
|
}
|
|
1451
1533
|
else {
|
|
1534
|
+
if (bufferState.buffer.length === 0) {
|
|
1535
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
|
|
1536
|
+
return { isComplete: false };
|
|
1537
|
+
}
|
|
1452
1538
|
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
1453
1539
|
}
|
|
1454
1540
|
if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {
|