@onekeyfe/hd-transport-web-device 1.2.0-alpha.17 → 1.2.0-alpha.171
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 +551 -27
- package/__tests__/webusb-protocol-cache.test.ts +261 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +363 -11
- 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 -4
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +169 -15
- package/dist/index.js +465 -296
- package/dist/transportLog.d.ts +1 -6
- package/dist/transportLog.d.ts.map +1 -1
- package/dist/webusb.d.ts +25 -11
- 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 +291 -130
- package/src/transportLog.ts +1 -11
- package/src/webusb.ts +345 -205
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,39 @@ 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
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
56
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
57
|
+
let activeWebUsbTransport;
|
|
58
|
+
let usbDisconnectListenerAttached = false;
|
|
59
|
+
function registerActiveWebUsbTransport(instance, usb) {
|
|
60
|
+
activeWebUsbTransport = instance;
|
|
61
|
+
if (usbDisconnectListenerAttached)
|
|
62
|
+
return;
|
|
63
|
+
usbDisconnectListenerAttached = true;
|
|
64
|
+
usb.addEventListener('disconnect', event => {
|
|
65
|
+
if (!event.device)
|
|
66
|
+
return;
|
|
67
|
+
const path = hdShared.resolveOneKeyUsbDevicePath(event.device);
|
|
68
|
+
if (!path)
|
|
69
|
+
return;
|
|
70
|
+
activeWebUsbTransport === null || activeWebUsbTransport === void 0 ? void 0 : activeWebUsbTransport.markProtocolStale(path);
|
|
71
|
+
activeWebUsbTransport === null || activeWebUsbTransport === void 0 ? void 0 : activeWebUsbTransport.emitDeviceDisconnect(path, event.device);
|
|
72
|
+
});
|
|
65
73
|
}
|
|
66
|
-
class WebUsbTransport {
|
|
74
|
+
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
67
75
|
constructor() {
|
|
76
|
+
super({
|
|
77
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
78
|
+
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
79
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
80
|
+
});
|
|
68
81
|
this.deviceProtocol = new Map();
|
|
69
82
|
this.deviceProtocolHints = new Map();
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
83
|
+
this.staleProtocolPaths = new Set();
|
|
84
|
+
this.acquiredPaths = new Set();
|
|
85
|
+
this.probedDeviceObjects = new Map();
|
|
73
86
|
this.deviceEndpoints = new Map();
|
|
74
|
-
this.mockSerialPaths = new WeakMap();
|
|
75
|
-
this.mockSerialCounter = 0;
|
|
76
87
|
this.name = 'WebUsbTransport';
|
|
77
88
|
this.stopped = false;
|
|
78
89
|
this.configured = false;
|
|
@@ -81,13 +92,27 @@ class WebUsbTransport {
|
|
|
81
92
|
this.endpointId = ENDPOINT_ID;
|
|
82
93
|
this.interfaceId = INTERFACE_ID;
|
|
83
94
|
}
|
|
84
|
-
init(logger) {
|
|
95
|
+
init(logger, emitter) {
|
|
85
96
|
this.Log = logger;
|
|
97
|
+
this.emitter = emitter;
|
|
86
98
|
const { usb } = navigator;
|
|
87
99
|
if (!usb) {
|
|
88
100
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
|
|
89
101
|
}
|
|
90
102
|
this.usb = usb;
|
|
103
|
+
registerActiveWebUsbTransport(this, usb);
|
|
104
|
+
}
|
|
105
|
+
emitDeviceDisconnect(path, device) {
|
|
106
|
+
var _a, _b;
|
|
107
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
108
|
+
name: (_b = device === null || device === void 0 ? void 0 : device.productName) !== null && _b !== void 0 ? _b : '',
|
|
109
|
+
id: path,
|
|
110
|
+
connectId: path,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
markProtocolStale(path) {
|
|
114
|
+
this.staleProtocolPaths.add(path);
|
|
115
|
+
this.probedDeviceObjects.delete(path);
|
|
91
116
|
}
|
|
92
117
|
configure(signedData) {
|
|
93
118
|
const messages = parseConfigure$1(signedData);
|
|
@@ -95,9 +120,18 @@ class WebUsbTransport {
|
|
|
95
120
|
this.messages = messages;
|
|
96
121
|
}
|
|
97
122
|
configureProtocolV2(signedData) {
|
|
123
|
+
var _a;
|
|
124
|
+
const schemaSource = typeof signedData === 'string'
|
|
125
|
+
? signedData
|
|
126
|
+
: (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
|
|
127
|
+
if (schemaSource === this.protocolV2SchemaSource)
|
|
128
|
+
return;
|
|
129
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
98
130
|
this.messagesV2 = parseConfigure$1(signedData);
|
|
99
|
-
this.
|
|
100
|
-
|
|
131
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
132
|
+
if (!hadProtocolV2Schema)
|
|
133
|
+
return;
|
|
134
|
+
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
135
|
}
|
|
102
136
|
promptDeviceAccess() {
|
|
103
137
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -119,20 +153,6 @@ class WebUsbTransport {
|
|
|
119
153
|
return this.deviceList;
|
|
120
154
|
});
|
|
121
155
|
}
|
|
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
156
|
getConnectedDevices() {
|
|
137
157
|
return __awaiter(this, void 0, void 0, function* () {
|
|
138
158
|
if (!this.usb)
|
|
@@ -142,41 +162,88 @@ class WebUsbTransport {
|
|
|
142
162
|
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
143
163
|
return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
144
164
|
});
|
|
145
|
-
this.deviceList = onekeyDevices.
|
|
146
|
-
const path =
|
|
147
|
-
|
|
165
|
+
this.deviceList = onekeyDevices.flatMap(device => {
|
|
166
|
+
const path = hdShared.resolveOneKeyUsbDevicePath(device);
|
|
167
|
+
if (!path)
|
|
168
|
+
return [];
|
|
169
|
+
const protocolHint = hdShared.inferProtocolHintFromUsbId(device.vendorId, device.productId);
|
|
148
170
|
if (protocolHint) {
|
|
149
171
|
this.deviceProtocolHints.set(path, protocolHint);
|
|
150
172
|
}
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
return [
|
|
174
|
+
{
|
|
175
|
+
path,
|
|
176
|
+
device,
|
|
177
|
+
commType: 'webusb',
|
|
178
|
+
},
|
|
179
|
+
];
|
|
156
180
|
});
|
|
157
181
|
return this.deviceList;
|
|
158
182
|
});
|
|
159
183
|
}
|
|
160
184
|
acquire(input) {
|
|
161
|
-
var _a, _b, _c;
|
|
185
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
162
186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
163
187
|
if (!input.path)
|
|
164
188
|
return;
|
|
165
189
|
try {
|
|
190
|
+
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
166
191
|
yield this.closeOpenDevice(input.path);
|
|
167
192
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.
|
|
193
|
+
if (input.skipProtocolProbe) {
|
|
194
|
+
if (!input.expectedProtocol) {
|
|
195
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected protocol');
|
|
196
|
+
}
|
|
197
|
+
this.staleProtocolPaths.delete(input.path);
|
|
198
|
+
this.deviceProtocol.set(input.path, input.expectedProtocol);
|
|
199
|
+
const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
200
|
+
if (currentDevice) {
|
|
201
|
+
this.probedDeviceObjects.set(input.path, currentDevice);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (input.forceProtocolDetection) {
|
|
205
|
+
this.staleProtocolPaths.delete(input.path);
|
|
206
|
+
this.deviceProtocol.delete(input.path);
|
|
207
|
+
this.deviceProtocolHints.delete(input.path);
|
|
208
|
+
}
|
|
209
|
+
if (!input.skipProtocolProbe && this.staleProtocolPaths.has(input.path)) {
|
|
210
|
+
this.staleProtocolPaths.delete(input.path);
|
|
211
|
+
this.deviceProtocol.delete(input.path);
|
|
174
212
|
}
|
|
175
|
-
|
|
213
|
+
if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
|
|
214
|
+
const currentDevice = (_c = this.deviceList.find(d => d.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
|
|
215
|
+
if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
|
|
216
|
+
this.deviceProtocol.delete(input.path);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const cachedProtocol = this.deviceProtocol.get(input.path);
|
|
220
|
+
if (!input.skipProtocolProbe &&
|
|
221
|
+
cachedProtocol &&
|
|
222
|
+
input.expectedProtocol &&
|
|
223
|
+
cachedProtocol !== input.expectedProtocol) {
|
|
224
|
+
this.deviceProtocol.delete(input.path);
|
|
225
|
+
}
|
|
226
|
+
if (!this.deviceProtocol.has(input.path)) {
|
|
227
|
+
const usbDevice = (_d = this.deviceList.find(device => device.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
|
|
228
|
+
const protocolHint = input.expectedProtocol
|
|
229
|
+
? undefined
|
|
230
|
+
: (_f = (_e = input.protocolHint) !== null && _e !== void 0 ? _e : this.deviceProtocolHints.get(input.path)) !== null && _f !== void 0 ? _f : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
|
|
231
|
+
if (protocolHint) {
|
|
232
|
+
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
233
|
+
}
|
|
234
|
+
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
235
|
+
const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
|
|
236
|
+
if (probedDevice) {
|
|
237
|
+
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
this.acquiredPaths.add(input.path);
|
|
176
241
|
return yield Promise.resolve(input.path);
|
|
177
242
|
}
|
|
178
243
|
catch (e) {
|
|
179
244
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
245
|
+
this.acquiredPaths.delete(input.path);
|
|
246
|
+
yield this.closeOpenDevice(input.path);
|
|
180
247
|
throw e;
|
|
181
248
|
}
|
|
182
249
|
});
|
|
@@ -184,29 +251,53 @@ class WebUsbTransport {
|
|
|
184
251
|
createProtocolMismatchError(expected) {
|
|
185
252
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
186
253
|
}
|
|
254
|
+
createProtocolProbeTimeoutError(expected, attempts) {
|
|
255
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
256
|
+
}
|
|
187
257
|
createProtocolDetectionError() {
|
|
188
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
258
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
189
259
|
}
|
|
190
260
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
261
|
+
var _a;
|
|
191
262
|
return __awaiter(this, void 0, void 0, function* () {
|
|
192
263
|
if (expectedProtocol === 'V1') {
|
|
193
264
|
if (yield this.probeProtocolV1(path)) {
|
|
194
265
|
this.deviceProtocol.set(path, 'V1');
|
|
195
266
|
return 'V1';
|
|
196
267
|
}
|
|
268
|
+
yield this.closeConnectionAfterProbe(path);
|
|
197
269
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
198
270
|
}
|
|
199
271
|
if (expectedProtocol === 'V2') {
|
|
200
|
-
|
|
201
|
-
|
|
272
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
273
|
+
if (yield this.probeProtocolV2(path)) {
|
|
274
|
+
this.deviceProtocol.set(path, 'V2');
|
|
275
|
+
return 'V2';
|
|
276
|
+
}
|
|
277
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
278
|
+
yield this.resetConnectionAfterProbe(path);
|
|
279
|
+
(_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}`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
yield this.closeConnectionAfterProbe(path);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
this.deviceProtocol.delete(path);
|
|
286
|
+
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
202
287
|
}
|
|
203
288
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
204
|
-
for (const protocol of probeOrder) {
|
|
289
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
205
290
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
206
291
|
if (detected) {
|
|
207
292
|
this.deviceProtocol.set(path, protocol);
|
|
208
293
|
return protocol;
|
|
209
294
|
}
|
|
295
|
+
if (index < probeOrder.length - 1) {
|
|
296
|
+
yield this.resetConnectionAfterProbe(path);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
yield this.closeConnectionAfterProbe(path);
|
|
300
|
+
}
|
|
210
301
|
}
|
|
211
302
|
this.deviceProtocol.delete(path);
|
|
212
303
|
throw this.createProtocolDetectionError();
|
|
@@ -273,7 +364,7 @@ class WebUsbTransport {
|
|
|
273
364
|
};
|
|
274
365
|
}
|
|
275
366
|
connectToDevice(path, first) {
|
|
276
|
-
var _a
|
|
367
|
+
var _a;
|
|
277
368
|
return __awaiter(this, void 0, void 0, function* () {
|
|
278
369
|
let device = yield this.findDevice(path);
|
|
279
370
|
if (!device.opened) {
|
|
@@ -297,33 +388,30 @@ class WebUsbTransport {
|
|
|
297
388
|
}
|
|
298
389
|
const endpoints = this.discoverEndpoints(device);
|
|
299
390
|
this.deviceEndpoints.set(path, endpoints);
|
|
300
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
301
|
-
this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
302
391
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
303
392
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
304
393
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
305
394
|
});
|
|
306
395
|
}
|
|
307
396
|
closeOpenDevice(path) {
|
|
308
|
-
var _a, _b, _c, _d
|
|
397
|
+
var _a, _b, _c, _d;
|
|
309
398
|
return __awaiter(this, void 0, void 0, function* () {
|
|
310
|
-
(_a = this.
|
|
311
|
-
const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
399
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
312
400
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
313
401
|
return;
|
|
314
402
|
const endpoints = this.deviceEndpoints.get(path);
|
|
315
|
-
const ifaceNum = (
|
|
403
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
316
404
|
try {
|
|
317
405
|
yield current.releaseInterface(ifaceNum);
|
|
318
406
|
}
|
|
319
407
|
catch (error) {
|
|
320
|
-
(
|
|
408
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
321
409
|
}
|
|
322
410
|
try {
|
|
323
411
|
yield current.close();
|
|
324
412
|
}
|
|
325
413
|
catch (error) {
|
|
326
|
-
(
|
|
414
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
327
415
|
}
|
|
328
416
|
});
|
|
329
417
|
}
|
|
@@ -338,8 +426,18 @@ class WebUsbTransport {
|
|
|
338
426
|
}
|
|
339
427
|
});
|
|
340
428
|
}
|
|
429
|
+
assertAcquired(path) {
|
|
430
|
+
if (!this.acquiredPaths.has(path)) {
|
|
431
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
341
434
|
post(session, name, data) {
|
|
342
435
|
return __awaiter(this, void 0, void 0, function* () {
|
|
436
|
+
this.assertAcquired(session);
|
|
437
|
+
if (this.deviceProtocol.get(session) === 'V2') {
|
|
438
|
+
yield this.sendProtocolV2UsbFlowControl(session, name, data);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
343
441
|
yield this.call(session, name, data);
|
|
344
442
|
});
|
|
345
443
|
}
|
|
@@ -368,6 +466,7 @@ class WebUsbTransport {
|
|
|
368
466
|
var _a;
|
|
369
467
|
return __awaiter(this, void 0, void 0, function* () {
|
|
370
468
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
469
|
+
this.staleProtocolPaths.add(path);
|
|
371
470
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
372
471
|
try {
|
|
373
472
|
const currentDevice = yield this.findDevice(path);
|
|
@@ -485,34 +584,33 @@ class WebUsbTransport {
|
|
|
485
584
|
throw lastError;
|
|
486
585
|
});
|
|
487
586
|
}
|
|
488
|
-
|
|
489
|
-
var _a
|
|
587
|
+
transferInOnce(path, length) {
|
|
588
|
+
var _a;
|
|
490
589
|
return __awaiter(this, void 0, void 0, function* () {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
const device = yield this.findDevice(path);
|
|
496
|
-
if (device.opened) {
|
|
497
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
498
|
-
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
499
|
-
try {
|
|
500
|
-
yield device.releaseInterface(ifaceNum);
|
|
501
|
-
}
|
|
502
|
-
catch (error) {
|
|
503
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
504
|
-
}
|
|
505
|
-
yield device.close();
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
590
|
+
const device = yield this.findDevice(path);
|
|
591
|
+
if (!device.opened) {
|
|
592
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
510
593
|
}
|
|
594
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
595
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
596
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
597
|
+
return this.getTransferInData(result);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
closeConnectionAfterProbe(path) {
|
|
601
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
602
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
603
|
+
yield this.closeOpenDevice(path);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
resetConnectionAfterProbe(path) {
|
|
607
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
608
|
+
yield this.closeConnectionAfterProbe(path);
|
|
511
609
|
yield this.getConnectedDevices();
|
|
512
610
|
yield this.connect(path, false);
|
|
513
611
|
});
|
|
514
612
|
}
|
|
515
|
-
withProtocolReadTimeout(
|
|
613
|
+
withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
|
|
516
614
|
return __awaiter(this, void 0, void 0, function* () {
|
|
517
615
|
let timer;
|
|
518
616
|
let timedOut = false;
|
|
@@ -527,11 +625,19 @@ class WebUsbTransport {
|
|
|
527
625
|
return yield Promise.race([
|
|
528
626
|
guardedPromise,
|
|
529
627
|
new Promise((_, reject) => {
|
|
530
|
-
timer = setTimeout(() => {
|
|
628
|
+
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
531
629
|
timedOut = true;
|
|
532
630
|
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
631
|
+
if (protocol === 'V1') {
|
|
632
|
+
try {
|
|
633
|
+
yield this.resetConnectionAfterProbe(path);
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
533
639
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
534
|
-
}, timeoutMs);
|
|
640
|
+
}), timeoutMs);
|
|
535
641
|
}),
|
|
536
642
|
]);
|
|
537
643
|
}
|
|
@@ -547,7 +653,9 @@ class WebUsbTransport {
|
|
|
547
653
|
return false;
|
|
548
654
|
}
|
|
549
655
|
try {
|
|
550
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
656
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
657
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
658
|
+
});
|
|
551
659
|
return true;
|
|
552
660
|
}
|
|
553
661
|
catch (_error) {
|
|
@@ -562,10 +670,9 @@ class WebUsbTransport {
|
|
|
562
670
|
}
|
|
563
671
|
return transport.probeProtocolV2({
|
|
564
672
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
565
|
-
timeoutMs:
|
|
673
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
566
674
|
logger: this.Log,
|
|
567
675
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
568
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
569
676
|
});
|
|
570
677
|
});
|
|
571
678
|
}
|
|
@@ -574,6 +681,7 @@ class WebUsbTransport {
|
|
|
574
681
|
if (this.messages == null) {
|
|
575
682
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
576
683
|
}
|
|
684
|
+
this.assertAcquired(path);
|
|
577
685
|
const device = yield this.findDevice(path);
|
|
578
686
|
if (!device) {
|
|
579
687
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
@@ -582,9 +690,6 @@ class WebUsbTransport {
|
|
|
582
690
|
if (!protocol) {
|
|
583
691
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
584
692
|
}
|
|
585
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
586
|
-
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
587
|
-
}
|
|
588
693
|
if (protocol === 'V2') {
|
|
589
694
|
return this.callProtocolV2(path, name, data, options);
|
|
590
695
|
}
|
|
@@ -613,79 +718,8 @@ class WebUsbTransport {
|
|
|
613
718
|
});
|
|
614
719
|
}
|
|
615
720
|
callProtocolV2(path, name, data, options) {
|
|
616
|
-
var _a;
|
|
617
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
618
|
-
const protocolV1Messages = this.messages;
|
|
619
|
-
if (!this.messagesV2) {
|
|
620
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
621
|
-
}
|
|
622
|
-
if (!protocolV1Messages) {
|
|
623
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
624
|
-
}
|
|
625
|
-
let session = this.protocolV2Sessions.get(path);
|
|
626
|
-
if (!session) {
|
|
627
|
-
session = new transport.ProtocolV2Session({
|
|
628
|
-
schemas: {
|
|
629
|
-
protocolV1: protocolV1Messages,
|
|
630
|
-
protocolV2: this.messagesV2,
|
|
631
|
-
},
|
|
632
|
-
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
633
|
-
writeFrame: (frame) => this.transferOutOnce(path, frame),
|
|
634
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
635
|
-
logger: this.Log,
|
|
636
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
637
|
-
createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
638
|
-
});
|
|
639
|
-
this.protocolV2Sessions.set(path, session);
|
|
640
|
-
}
|
|
641
|
-
this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
642
|
-
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
643
|
-
try {
|
|
644
|
-
return yield session.call(name, data, options);
|
|
645
|
-
}
|
|
646
|
-
catch (error) {
|
|
647
|
-
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
648
|
-
if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
|
|
649
|
-
try {
|
|
650
|
-
yield this.resetConnectionAfterProbe(path);
|
|
651
|
-
}
|
|
652
|
-
catch (resetError) {
|
|
653
|
-
this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
658
|
-
finally {
|
|
659
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
receiveProtocolV2Frame(path, timeoutMs) {
|
|
664
721
|
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;
|
|
722
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
689
723
|
});
|
|
690
724
|
}
|
|
691
725
|
receiveData(path, timeoutMs) {
|
|
@@ -726,45 +760,81 @@ class WebUsbTransport {
|
|
|
726
760
|
});
|
|
727
761
|
}
|
|
728
762
|
release(path) {
|
|
729
|
-
var _a, _b;
|
|
730
763
|
return __awaiter(this, void 0, void 0, function* () {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
yield device.releaseInterface(ifaceNum);
|
|
735
|
-
yield device.close();
|
|
736
|
-
this.deviceProtocol.delete(path);
|
|
737
|
-
this.deviceProtocolHints.delete(path);
|
|
738
|
-
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
739
|
-
this.protocolV2Assemblers.delete(path);
|
|
740
|
-
this.protocolV2Sessions.delete(path);
|
|
741
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
764
|
+
this.acquiredPaths.delete(path);
|
|
765
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
766
|
+
yield this.closeOpenDevice(path);
|
|
742
767
|
this.deviceEndpoints.delete(path);
|
|
743
768
|
});
|
|
744
769
|
}
|
|
770
|
+
getProtocolV2UsbSchemas() {
|
|
771
|
+
if (!this.messages || !this.messagesV2) {
|
|
772
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
protocolV1: this.messages,
|
|
776
|
+
protocolV2: this.messagesV2,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
getProtocolV2UsbLogger() {
|
|
780
|
+
return this.Log;
|
|
781
|
+
}
|
|
782
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
783
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
784
|
+
yield this.transferOutOnce(path, frame);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
788
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
789
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
790
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
794
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
795
|
+
yield this.closeOpenDevice(path);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
799
|
+
var _a;
|
|
800
|
+
this.deviceProtocol.delete(path);
|
|
801
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
802
|
+
}
|
|
803
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
804
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
805
|
+
}
|
|
745
806
|
getProtocolType(path) {
|
|
746
807
|
return this.deviceProtocol.get(path);
|
|
747
808
|
}
|
|
748
809
|
}
|
|
749
810
|
|
|
750
|
-
const
|
|
751
|
-
function
|
|
752
|
-
|
|
811
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
812
|
+
function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
|
|
813
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
814
|
+
return fallbackPacketCapacity;
|
|
815
|
+
}
|
|
816
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
753
817
|
}
|
|
818
|
+
|
|
819
|
+
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
754
820
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
755
|
-
const
|
|
821
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
822
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
756
823
|
const BLE_WRITE_DELAY_MS = 5;
|
|
757
|
-
const
|
|
758
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
824
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
|
|
759
825
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
760
826
|
class ElectronBleTransport {
|
|
761
827
|
constructor() {
|
|
762
828
|
this.name = 'ElectronBleTransport';
|
|
763
829
|
this.configured = false;
|
|
764
830
|
this.runPromise = null;
|
|
831
|
+
this.runPromiseDeviceId = null;
|
|
765
832
|
this.connectedDevices = new Set();
|
|
766
833
|
this.deviceProtocol = new Map();
|
|
767
834
|
this.deviceProtocolHints = new Map();
|
|
835
|
+
this.confirmedProtocolV2 = new Set();
|
|
836
|
+
this.deviceMtus = new Map();
|
|
837
|
+
this.devicePacketCapacities = new Map();
|
|
768
838
|
this.v1Buffers = new Map();
|
|
769
839
|
this.v2Assemblers = new Map();
|
|
770
840
|
this.v2FrameQueues = new Map();
|
|
@@ -786,12 +856,13 @@ class ElectronBleTransport {
|
|
|
786
856
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
787
857
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
788
858
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
789
|
-
yield this.
|
|
859
|
+
yield this.releaseNative(uuid);
|
|
790
860
|
}
|
|
791
861
|
}),
|
|
792
862
|
});
|
|
863
|
+
this.warnedMissingRelease = false;
|
|
793
864
|
this.notificationCleanups = new Map();
|
|
794
|
-
this.
|
|
865
|
+
this.mtuCleanups = new Map();
|
|
795
866
|
this.notificationTokens = new Map();
|
|
796
867
|
this.nextNotificationToken = 1;
|
|
797
868
|
}
|
|
@@ -830,19 +901,26 @@ class ElectronBleTransport {
|
|
|
830
901
|
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
|
|
831
902
|
this.connectedDevices.delete(deviceId);
|
|
832
903
|
this.deviceProtocol.delete(deviceId);
|
|
904
|
+
this.deviceMtus.delete(deviceId);
|
|
905
|
+
this.devicePacketCapacities.delete(deviceId);
|
|
833
906
|
this.v1Buffers.delete(deviceId);
|
|
834
907
|
this.v2Assemblers.delete(deviceId);
|
|
835
908
|
this.resetProtocolV2Frames(deviceId);
|
|
836
909
|
this.notificationTokens.delete(deviceId);
|
|
910
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
911
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
|
|
912
|
+
this.runPromise = null;
|
|
913
|
+
this.runPromiseDeviceId = null;
|
|
914
|
+
}
|
|
837
915
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
838
916
|
if (notifyCleanup) {
|
|
839
917
|
notifyCleanup();
|
|
840
918
|
this.notificationCleanups.delete(deviceId);
|
|
841
919
|
}
|
|
842
|
-
const
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
this.
|
|
920
|
+
const mtuCleanup = this.mtuCleanups.get(deviceId);
|
|
921
|
+
if (mtuCleanup) {
|
|
922
|
+
mtuCleanup();
|
|
923
|
+
this.mtuCleanups.delete(deviceId);
|
|
846
924
|
}
|
|
847
925
|
}
|
|
848
926
|
init(logger, emitter) {
|
|
@@ -852,17 +930,43 @@ class ElectronBleTransport {
|
|
|
852
930
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
853
931
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble BLE API is not available. Please ensure you are running in Electron with Noble support.');
|
|
854
932
|
}
|
|
933
|
+
this.subscribeHostDisconnects();
|
|
855
934
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Transport initialized');
|
|
856
935
|
}
|
|
936
|
+
subscribeHostDisconnects() {
|
|
937
|
+
var _a, _b, _c;
|
|
938
|
+
(_a = this.hostDisconnectCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
939
|
+
this.hostDisconnectCleanup = (_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.onDeviceDisconnected((disconnectedDevice) => {
|
|
940
|
+
var _a, _b, _c;
|
|
941
|
+
const uuid = disconnectedDevice === null || disconnectedDevice === void 0 ? void 0 : disconnectedDevice.id;
|
|
942
|
+
if (!uuid)
|
|
943
|
+
return;
|
|
944
|
+
this.cleanupDeviceState(uuid);
|
|
945
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Device link dropped:', uuid, (_b = disconnectedDevice.reason) !== null && _b !== void 0 ? _b : hdShared.EBleDisconnectReason.DeviceDisconnected);
|
|
946
|
+
(_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
947
|
+
name: disconnectedDevice.name,
|
|
948
|
+
id: uuid,
|
|
949
|
+
connectId: uuid,
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
}
|
|
857
953
|
configure(signedData) {
|
|
858
954
|
this._messages = parseConfigure(signedData);
|
|
859
955
|
this.configured = true;
|
|
860
956
|
}
|
|
861
957
|
configureProtocolV2(signedData) {
|
|
958
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
959
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
862
963
|
this._messagesV2 = parseConfigure(signedData);
|
|
863
|
-
this.
|
|
864
|
-
|
|
865
|
-
|
|
964
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
965
|
+
if (isReconfiguration) {
|
|
966
|
+
this.protocolV2Links
|
|
967
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
968
|
+
.catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
|
|
969
|
+
}
|
|
866
970
|
}
|
|
867
971
|
listen() {
|
|
868
972
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -880,10 +984,6 @@ class ElectronBleTransport {
|
|
|
880
984
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
|
|
881
985
|
for (const dev of devices) {
|
|
882
986
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
|
|
883
|
-
const protocolHint = inferProtocolHintFromDeviceName(dev.name);
|
|
884
|
-
if (protocolHint) {
|
|
885
|
-
this.deviceProtocolHints.set(dev.id, protocolHint);
|
|
886
|
-
}
|
|
887
987
|
}
|
|
888
988
|
return devices.map(device => toBleDescriptor(device));
|
|
889
989
|
}
|
|
@@ -894,7 +994,7 @@ class ElectronBleTransport {
|
|
|
894
994
|
});
|
|
895
995
|
}
|
|
896
996
|
acquire(input) {
|
|
897
|
-
var _a, _b, _c, _d, _e, _f;
|
|
997
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
898
998
|
return __awaiter(this, void 0, void 0, function* () {
|
|
899
999
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
900
1000
|
if (!uuid) {
|
|
@@ -906,8 +1006,8 @@ class ElectronBleTransport {
|
|
|
906
1006
|
if (forceCleanRunPromise && this.runPromise) {
|
|
907
1007
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
908
1008
|
this.runPromise.reject(error);
|
|
909
|
-
this.rejectAllProtocolV2Frames(error);
|
|
910
1009
|
this.runPromise = null;
|
|
1010
|
+
this.runPromiseDeviceId = null;
|
|
911
1011
|
}
|
|
912
1012
|
try {
|
|
913
1013
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -919,7 +1019,7 @@ class ElectronBleTransport {
|
|
|
919
1019
|
}
|
|
920
1020
|
const protocolHint = expectedProtocol
|
|
921
1021
|
? undefined
|
|
922
|
-
: (_b =
|
|
1022
|
+
: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid);
|
|
923
1023
|
if (protocolHint) {
|
|
924
1024
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
925
1025
|
}
|
|
@@ -930,52 +1030,64 @@ class ElectronBleTransport {
|
|
|
930
1030
|
catch (error) {
|
|
931
1031
|
this.handleBluetoothError(error);
|
|
932
1032
|
}
|
|
1033
|
+
const mtuCleanup = this.createMtuSubscription(uuid);
|
|
1034
|
+
if (mtuCleanup) {
|
|
1035
|
+
this.mtuCleanups.set(uuid, mtuCleanup);
|
|
1036
|
+
}
|
|
933
1037
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
934
|
-
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
|
|
1038
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
935
1039
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
1040
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
936
1041
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
937
1042
|
this.notificationCleanups.set(uuid, cleanup);
|
|
938
|
-
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
939
|
-
var _a;
|
|
940
|
-
if (disconnectedDevice.id === uuid) {
|
|
941
|
-
this.cleanupDeviceState(uuid);
|
|
942
|
-
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
|
|
943
|
-
name: disconnectedDevice.name,
|
|
944
|
-
id: disconnectedDevice.id,
|
|
945
|
-
connectId: disconnectedDevice.id,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
950
1043
|
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
951
|
-
(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1044
|
+
if (protocolType === 'V2') {
|
|
1045
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[Electron BLE] Protocol V2 write configured', {
|
|
1046
|
+
writeMode: 'withoutResponse',
|
|
1047
|
+
negotiatedMtu: this.deviceMtus.get(uuid),
|
|
1048
|
+
packetCapacity: (_d = this.devicePacketCapacities.get(uuid)) !== null && _d !== void 0 ? _d : BLE_PACKET_SIZE_FALLBACK,
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
956
1051
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
957
1052
|
}
|
|
958
1053
|
catch (error) {
|
|
959
|
-
(
|
|
1054
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.error('[Electron BLE] acquire failed:', error);
|
|
960
1055
|
try {
|
|
961
|
-
if (((
|
|
1056
|
+
if (((_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
962
1057
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
963
1058
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
964
1059
|
}
|
|
965
1060
|
}
|
|
966
1061
|
catch (cleanupError) {
|
|
967
|
-
(
|
|
1062
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
968
1063
|
}
|
|
969
1064
|
this.cleanupDeviceState(uuid);
|
|
970
1065
|
throw error;
|
|
971
1066
|
}
|
|
972
1067
|
});
|
|
973
1068
|
}
|
|
974
|
-
release(id) {
|
|
975
|
-
var _a
|
|
1069
|
+
release(id, _onclose, keepSession) {
|
|
1070
|
+
var _a;
|
|
976
1071
|
return __awaiter(this, void 0, void 0, function* () {
|
|
977
1072
|
try {
|
|
978
1073
|
yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
1074
|
+
yield this.releaseLogical(id, keepSession);
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
|
|
1078
|
+
this.cleanupDeviceState(id);
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
disconnect(id) {
|
|
1083
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1084
|
+
return this.releaseNative(id);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
releaseNative(id) {
|
|
1088
|
+
var _a, _b;
|
|
1089
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1090
|
+
try {
|
|
979
1091
|
if (this.connectedDevices.has(id)) {
|
|
980
1092
|
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
981
1093
|
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
@@ -990,11 +1102,36 @@ class ElectronBleTransport {
|
|
|
990
1102
|
}
|
|
991
1103
|
});
|
|
992
1104
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1105
|
+
releaseLogical(id, keepSession) {
|
|
1106
|
+
var _a, _b, _c, _d;
|
|
1107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1108
|
+
try {
|
|
1109
|
+
if (!this.connectedDevices.has(id))
|
|
1110
|
+
return;
|
|
1111
|
+
this.cleanupDeviceState(id);
|
|
1112
|
+
const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
|
|
1113
|
+
if (!release) {
|
|
1114
|
+
if (!this.warnedMissingRelease) {
|
|
1115
|
+
this.warnedMissingRelease = true;
|
|
1116
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
|
|
1117
|
+
'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
|
|
1118
|
+
}
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
yield release(id, keepSession);
|
|
1122
|
+
}
|
|
1123
|
+
catch (error) {
|
|
1124
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
|
|
1125
|
+
this.cleanupDeviceState(id);
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
createProtocolMismatchError(expected, uuid) {
|
|
1130
|
+
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
1131
|
+
return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
995
1132
|
}
|
|
996
1133
|
createProtocolDetectionError() {
|
|
997
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1134
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
998
1135
|
}
|
|
999
1136
|
clearProbeProtocol(uuid, protocol) {
|
|
1000
1137
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1010,12 +1147,16 @@ class ElectronBleTransport {
|
|
|
1010
1147
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1011
1148
|
return 'V1';
|
|
1012
1149
|
}
|
|
1013
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1150
|
+
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
1014
1151
|
}
|
|
1015
1152
|
if (expectedProtocol === 'V2') {
|
|
1016
|
-
this.
|
|
1017
|
-
|
|
1018
|
-
|
|
1153
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
1154
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1155
|
+
this.confirmedProtocolV2.add(uuid);
|
|
1156
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1157
|
+
return 'V2';
|
|
1158
|
+
}
|
|
1159
|
+
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
1019
1160
|
}
|
|
1020
1161
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1021
1162
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1026,6 +1167,9 @@ class ElectronBleTransport {
|
|
|
1026
1167
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1027
1168
|
if (detected) {
|
|
1028
1169
|
this.deviceProtocol.set(uuid, protocol);
|
|
1170
|
+
if (protocol === 'V2') {
|
|
1171
|
+
this.confirmedProtocolV2.add(uuid);
|
|
1172
|
+
}
|
|
1029
1173
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1030
1174
|
return protocol;
|
|
1031
1175
|
}
|
|
@@ -1049,38 +1193,22 @@ class ElectronBleTransport {
|
|
|
1049
1193
|
});
|
|
1050
1194
|
}
|
|
1051
1195
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1052
|
-
var _a
|
|
1196
|
+
var _a;
|
|
1053
1197
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1054
1198
|
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1055
1199
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1056
1200
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1057
1201
|
this.resetProtocolV2Frames(uuid);
|
|
1058
|
-
if (this.runPromise) {
|
|
1202
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1059
1203
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1060
1204
|
this.runPromise.reject(error);
|
|
1061
1205
|
this.runPromise = null;
|
|
1206
|
+
this.runPromiseDeviceId = null;
|
|
1062
1207
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
this.notificationCleanups.delete(uuid);
|
|
1067
|
-
}
|
|
1068
|
-
this.notificationTokens.delete(uuid);
|
|
1069
|
-
try {
|
|
1070
|
-
yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
|
|
1071
|
-
}
|
|
1072
|
-
catch (error) {
|
|
1073
|
-
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1074
|
-
}
|
|
1075
|
-
try {
|
|
1076
|
-
yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
|
|
1077
|
-
}
|
|
1078
|
-
catch (error) {
|
|
1079
|
-
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1080
|
-
throw error;
|
|
1208
|
+
if (!this.notificationCleanups.has(uuid)) {
|
|
1209
|
+
const cleanup = this.createNotificationSubscription(uuid);
|
|
1210
|
+
this.notificationCleanups.set(uuid, cleanup);
|
|
1081
1211
|
}
|
|
1082
|
-
const cleanup = this.createNotificationSubscription(uuid);
|
|
1083
|
-
this.notificationCleanups.set(uuid, cleanup);
|
|
1084
1212
|
});
|
|
1085
1213
|
}
|
|
1086
1214
|
probeProtocolV1(uuid) {
|
|
@@ -1091,12 +1219,12 @@ class ElectronBleTransport {
|
|
|
1091
1219
|
}
|
|
1092
1220
|
try {
|
|
1093
1221
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1094
|
-
yield this.callProtocolV1(uuid, '
|
|
1222
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1095
1223
|
return true;
|
|
1096
1224
|
}
|
|
1097
1225
|
catch (error) {
|
|
1098
1226
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1099
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1227
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1100
1228
|
return false;
|
|
1101
1229
|
}
|
|
1102
1230
|
});
|
|
@@ -1126,25 +1254,6 @@ class ElectronBleTransport {
|
|
|
1126
1254
|
return detected;
|
|
1127
1255
|
});
|
|
1128
1256
|
}
|
|
1129
|
-
writeWithChunking(uuid, hexData) {
|
|
1130
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1131
|
-
const totalBytes = hexData.length / 2;
|
|
1132
|
-
if (totalBytes <= BLE_PACKET_SIZE) {
|
|
1133
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1134
|
-
yield this.writeOnce(uuid, hexData);
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
for (let offset = 0; offset < hexData.length;) {
|
|
1138
|
-
const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
|
|
1139
|
-
const chunkHex = hexData.substring(offset, offset + chunkHexLen);
|
|
1140
|
-
offset += chunkHexLen;
|
|
1141
|
-
yield this.writeOnce(uuid, chunkHex);
|
|
1142
|
-
if (offset < hexData.length) {
|
|
1143
|
-
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
1257
|
writeOnce(uuid, hexData) {
|
|
1149
1258
|
var _a;
|
|
1150
1259
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1152,7 +1261,51 @@ class ElectronBleTransport {
|
|
|
1152
1261
|
if (!nobleBle) {
|
|
1153
1262
|
throw new Error('Noble BLE API not available');
|
|
1154
1263
|
}
|
|
1155
|
-
yield nobleBle.write(uuid, hexData);
|
|
1264
|
+
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
refreshBlePacketCapacity(uuid) {
|
|
1268
|
+
var _a, _b;
|
|
1269
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1270
|
+
const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
|
|
1271
|
+
this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
updateBlePacketCapacity(uuid, mtu) {
|
|
1275
|
+
const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
1276
|
+
if (typeof mtu === 'number') {
|
|
1277
|
+
this.deviceMtus.set(uuid, mtu);
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
this.deviceMtus.delete(uuid);
|
|
1281
|
+
}
|
|
1282
|
+
this.devicePacketCapacities.set(uuid, packetCapacity);
|
|
1283
|
+
}
|
|
1284
|
+
createMtuSubscription(uuid) {
|
|
1285
|
+
var _a, _b;
|
|
1286
|
+
const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
|
|
1287
|
+
if (!onMtuChanged)
|
|
1288
|
+
return undefined;
|
|
1289
|
+
return onMtuChanged(device => {
|
|
1290
|
+
if (device.id === uuid) {
|
|
1291
|
+
this.updateBlePacketCapacity(uuid, device.mtu);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
1296
|
+
var _a;
|
|
1297
|
+
const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
|
|
1298
|
+
const shouldPace = !context.highThroughput;
|
|
1299
|
+
return transport.writeProtocolV2BleFrame({
|
|
1300
|
+
frame,
|
|
1301
|
+
packetCapacity,
|
|
1302
|
+
assertActive: assertCurrentGeneration,
|
|
1303
|
+
signal: context.signal,
|
|
1304
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1305
|
+
initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
|
|
1306
|
+
burstSize: shouldPace ? 1 : undefined,
|
|
1307
|
+
burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
|
|
1308
|
+
writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
|
|
1156
1309
|
});
|
|
1157
1310
|
}
|
|
1158
1311
|
handleNotification(deviceId, hexData) {
|
|
@@ -1163,7 +1316,7 @@ class ElectronBleTransport {
|
|
|
1163
1316
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1164
1317
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
1165
1318
|
}
|
|
1166
|
-
else if (this.runPromise) {
|
|
1319
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1167
1320
|
this.runPromise.reject(error);
|
|
1168
1321
|
}
|
|
1169
1322
|
return;
|
|
@@ -1215,16 +1368,8 @@ class ElectronBleTransport {
|
|
|
1215
1368
|
}
|
|
1216
1369
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1217
1370
|
}
|
|
1218
|
-
rejectAllProtocolV2Frames(error) {
|
|
1219
|
-
this.v2FrameQueues.clear();
|
|
1220
|
-
for (const framePromise of this.v2FramePromises.values()) {
|
|
1221
|
-
framePromise.reject(error);
|
|
1222
|
-
}
|
|
1223
|
-
this.v2FramePromises.clear();
|
|
1224
|
-
}
|
|
1225
1371
|
resetProtocolV2Frames(uuid) {
|
|
1226
|
-
this.
|
|
1227
|
-
this.v2FramePromises.delete(uuid);
|
|
1372
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1228
1373
|
}
|
|
1229
1374
|
rejectProtocolV2Frames(uuid, error) {
|
|
1230
1375
|
this.v2FrameQueues.delete(uuid);
|
|
@@ -1257,17 +1402,19 @@ class ElectronBleTransport {
|
|
|
1257
1402
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1258
1403
|
if (result.error) {
|
|
1259
1404
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1260
|
-
if (this.runPromise) {
|
|
1405
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1261
1406
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1262
1407
|
}
|
|
1263
1408
|
return;
|
|
1264
1409
|
}
|
|
1265
|
-
if (result.isComplete &&
|
|
1410
|
+
if (result.isComplete &&
|
|
1411
|
+
result.completePacket &&
|
|
1412
|
+
this.runPromise &&
|
|
1413
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1266
1414
|
this.runPromise.resolve(result.completePacket);
|
|
1267
1415
|
}
|
|
1268
1416
|
}
|
|
1269
1417
|
call(uuid, name, data, options) {
|
|
1270
|
-
var _a;
|
|
1271
1418
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1272
1419
|
if (!this._messages) {
|
|
1273
1420
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1279,15 +1426,21 @@ class ElectronBleTransport {
|
|
|
1279
1426
|
if (!protocol) {
|
|
1280
1427
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1281
1428
|
}
|
|
1282
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
1283
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
|
|
1284
|
-
}
|
|
1285
1429
|
if (protocol === 'V2') {
|
|
1286
1430
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1287
1431
|
}
|
|
1288
1432
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1289
1433
|
});
|
|
1290
1434
|
}
|
|
1435
|
+
post(uuid, name, data) {
|
|
1436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1437
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1438
|
+
yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
yield this.callProtocolV1(uuid, name, data);
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1291
1444
|
callProtocolV1(uuid, name, data, options) {
|
|
1292
1445
|
var _a, _b;
|
|
1293
1446
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1301,6 +1454,7 @@ class ElectronBleTransport {
|
|
|
1301
1454
|
const runPromise = hdShared.createDeferred();
|
|
1302
1455
|
runPromise.promise.catch(() => undefined);
|
|
1303
1456
|
this.runPromise = runPromise;
|
|
1457
|
+
this.runPromiseDeviceId = uuid;
|
|
1304
1458
|
const messages = this._messages;
|
|
1305
1459
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1306
1460
|
let timeout;
|
|
@@ -1339,6 +1493,17 @@ class ElectronBleTransport {
|
|
|
1339
1493
|
}
|
|
1340
1494
|
catch (e) {
|
|
1341
1495
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1496
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1497
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1498
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1499
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1500
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1501
|
+
this.notificationCleanups.delete(uuid);
|
|
1502
|
+
this.notificationTokens.delete(uuid);
|
|
1503
|
+
if (!isProbeTimeout) {
|
|
1504
|
+
yield this.releaseNative(uuid);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1342
1507
|
throw e;
|
|
1343
1508
|
}
|
|
1344
1509
|
finally {
|
|
@@ -1346,22 +1511,23 @@ class ElectronBleTransport {
|
|
|
1346
1511
|
clearTimeout(timeout);
|
|
1347
1512
|
if (this.runPromise === runPromise) {
|
|
1348
1513
|
this.runPromise = null;
|
|
1514
|
+
this.runPromiseDeviceId = null;
|
|
1349
1515
|
}
|
|
1350
1516
|
}
|
|
1351
1517
|
});
|
|
1352
1518
|
}
|
|
1353
1519
|
callProtocolV2(uuid, name, data, options) {
|
|
1354
|
-
var _a
|
|
1520
|
+
var _a;
|
|
1355
1521
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1356
1522
|
if (!this._messages || !this._messagesV2) {
|
|
1357
1523
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1358
1524
|
}
|
|
1359
|
-
const callOptions =
|
|
1525
|
+
const callOptions = options;
|
|
1360
1526
|
try {
|
|
1361
1527
|
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
1362
1528
|
}
|
|
1363
1529
|
catch (e) {
|
|
1364
|
-
(
|
|
1530
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1365
1531
|
throw e;
|
|
1366
1532
|
}
|
|
1367
1533
|
});
|
|
@@ -1376,6 +1542,7 @@ class ElectronBleTransport {
|
|
|
1376
1542
|
};
|
|
1377
1543
|
return {
|
|
1378
1544
|
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1545
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1379
1546
|
generation,
|
|
1380
1547
|
prepareCall: () => {
|
|
1381
1548
|
var _a;
|
|
@@ -1383,10 +1550,7 @@ class ElectronBleTransport {
|
|
|
1383
1550
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1384
1551
|
this.resetProtocolV2Frames(uuid);
|
|
1385
1552
|
},
|
|
1386
|
-
writeFrame: (frame) =>
|
|
1387
|
-
assertCurrentGeneration();
|
|
1388
|
-
return this.writeWithChunking(uuid, transport.bytesToHex(frame));
|
|
1389
|
-
},
|
|
1553
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
1390
1554
|
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1391
1555
|
assertCurrentGeneration();
|
|
1392
1556
|
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
@@ -1406,6 +1570,7 @@ class ElectronBleTransport {
|
|
|
1406
1570
|
};
|
|
1407
1571
|
}
|
|
1408
1572
|
processProtocolV1Notification(deviceId, hexData) {
|
|
1573
|
+
var _a;
|
|
1409
1574
|
try {
|
|
1410
1575
|
if (typeof hexData !== 'string') {
|
|
1411
1576
|
return { isComplete: false, error: 'Invalid hexData type' };
|
|
@@ -1424,6 +1589,10 @@ class ElectronBleTransport {
|
|
|
1424
1589
|
bufferState.buffer = [...data.subarray(3)];
|
|
1425
1590
|
}
|
|
1426
1591
|
else {
|
|
1592
|
+
if (bufferState.buffer.length === 0) {
|
|
1593
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
|
|
1594
|
+
return { isComplete: false };
|
|
1595
|
+
}
|
|
1427
1596
|
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
1428
1597
|
}
|
|
1429
1598
|
if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {
|