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