@onekeyfe/hd-transport-web-device 1.2.0-alpha.16 → 1.2.0-alpha.160
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 +569 -27
- package/__tests__/webusb-protocol-cache.test.ts +244 -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 +20 -4
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +170 -15
- package/dist/index.js +498 -297
- 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 +342 -132
- package/src/transportLog.ts +1 -11
- package/src/webusb.ts +327 -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,74 @@ 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;
|
|
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
|
-
|
|
193
|
+
if (input.forceProtocolDetection) {
|
|
194
|
+
this.staleProtocolPaths.delete(input.path);
|
|
195
|
+
this.deviceProtocol.delete(input.path);
|
|
196
|
+
this.deviceProtocolHints.delete(input.path);
|
|
197
|
+
}
|
|
198
|
+
if (this.staleProtocolPaths.has(input.path)) {
|
|
199
|
+
this.staleProtocolPaths.delete(input.path);
|
|
200
|
+
this.deviceProtocol.delete(input.path);
|
|
201
|
+
}
|
|
202
|
+
if (this.deviceProtocol.has(input.path)) {
|
|
203
|
+
const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
204
|
+
if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
|
|
205
|
+
this.deviceProtocol.delete(input.path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const cachedProtocol = this.deviceProtocol.get(input.path);
|
|
209
|
+
if (cachedProtocol && input.expectedProtocol && cachedProtocol !== input.expectedProtocol) {
|
|
210
|
+
this.deviceProtocol.delete(input.path);
|
|
211
|
+
}
|
|
212
|
+
if (!this.deviceProtocol.has(input.path)) {
|
|
213
|
+
const usbDevice = (_c = this.deviceList.find(device => device.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
|
|
214
|
+
const protocolHint = input.expectedProtocol
|
|
215
|
+
? undefined
|
|
216
|
+
: (_e = (_d = input.protocolHint) !== null && _d !== void 0 ? _d : this.deviceProtocolHints.get(input.path)) !== null && _e !== void 0 ? _e : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
|
|
217
|
+
if (protocolHint) {
|
|
218
|
+
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
219
|
+
}
|
|
220
|
+
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
221
|
+
const probedDevice = (_f = this.deviceList.find(d => d.path === input.path)) === null || _f === void 0 ? void 0 : _f.device;
|
|
222
|
+
if (probedDevice) {
|
|
223
|
+
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
224
|
+
}
|
|
174
225
|
}
|
|
175
|
-
|
|
226
|
+
this.acquiredPaths.add(input.path);
|
|
176
227
|
return yield Promise.resolve(input.path);
|
|
177
228
|
}
|
|
178
229
|
catch (e) {
|
|
179
230
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
231
|
+
this.acquiredPaths.delete(input.path);
|
|
232
|
+
yield this.closeOpenDevice(input.path);
|
|
180
233
|
throw e;
|
|
181
234
|
}
|
|
182
235
|
});
|
|
@@ -184,29 +237,53 @@ class WebUsbTransport {
|
|
|
184
237
|
createProtocolMismatchError(expected) {
|
|
185
238
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
186
239
|
}
|
|
240
|
+
createProtocolProbeTimeoutError(expected, attempts) {
|
|
241
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
|
|
242
|
+
}
|
|
187
243
|
createProtocolDetectionError() {
|
|
188
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
244
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
189
245
|
}
|
|
190
246
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
247
|
+
var _a;
|
|
191
248
|
return __awaiter(this, void 0, void 0, function* () {
|
|
192
249
|
if (expectedProtocol === 'V1') {
|
|
193
250
|
if (yield this.probeProtocolV1(path)) {
|
|
194
251
|
this.deviceProtocol.set(path, 'V1');
|
|
195
252
|
return 'V1';
|
|
196
253
|
}
|
|
254
|
+
yield this.closeConnectionAfterProbe(path);
|
|
197
255
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
198
256
|
}
|
|
199
257
|
if (expectedProtocol === 'V2') {
|
|
200
|
-
|
|
201
|
-
|
|
258
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
259
|
+
if (yield this.probeProtocolV2(path)) {
|
|
260
|
+
this.deviceProtocol.set(path, 'V2');
|
|
261
|
+
return 'V2';
|
|
262
|
+
}
|
|
263
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
264
|
+
yield this.resetConnectionAfterProbe(path);
|
|
265
|
+
(_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}`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
yield this.closeConnectionAfterProbe(path);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this.deviceProtocol.delete(path);
|
|
272
|
+
throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
|
|
202
273
|
}
|
|
203
274
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
204
|
-
for (const protocol of probeOrder) {
|
|
275
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
205
276
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
206
277
|
if (detected) {
|
|
207
278
|
this.deviceProtocol.set(path, protocol);
|
|
208
279
|
return protocol;
|
|
209
280
|
}
|
|
281
|
+
if (index < probeOrder.length - 1) {
|
|
282
|
+
yield this.resetConnectionAfterProbe(path);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
yield this.closeConnectionAfterProbe(path);
|
|
286
|
+
}
|
|
210
287
|
}
|
|
211
288
|
this.deviceProtocol.delete(path);
|
|
212
289
|
throw this.createProtocolDetectionError();
|
|
@@ -273,7 +350,7 @@ class WebUsbTransport {
|
|
|
273
350
|
};
|
|
274
351
|
}
|
|
275
352
|
connectToDevice(path, first) {
|
|
276
|
-
var _a
|
|
353
|
+
var _a;
|
|
277
354
|
return __awaiter(this, void 0, void 0, function* () {
|
|
278
355
|
let device = yield this.findDevice(path);
|
|
279
356
|
if (!device.opened) {
|
|
@@ -297,33 +374,30 @@ class WebUsbTransport {
|
|
|
297
374
|
}
|
|
298
375
|
const endpoints = this.discoverEndpoints(device);
|
|
299
376
|
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
377
|
yield device.claimInterface(endpoints.interfaceNumber);
|
|
303
378
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
304
379
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
305
380
|
});
|
|
306
381
|
}
|
|
307
382
|
closeOpenDevice(path) {
|
|
308
|
-
var _a, _b, _c, _d
|
|
383
|
+
var _a, _b, _c, _d;
|
|
309
384
|
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;
|
|
385
|
+
const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
312
386
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
313
387
|
return;
|
|
314
388
|
const endpoints = this.deviceEndpoints.get(path);
|
|
315
|
-
const ifaceNum = (
|
|
389
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
316
390
|
try {
|
|
317
391
|
yield current.releaseInterface(ifaceNum);
|
|
318
392
|
}
|
|
319
393
|
catch (error) {
|
|
320
|
-
(
|
|
394
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
321
395
|
}
|
|
322
396
|
try {
|
|
323
397
|
yield current.close();
|
|
324
398
|
}
|
|
325
399
|
catch (error) {
|
|
326
|
-
(
|
|
400
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
327
401
|
}
|
|
328
402
|
});
|
|
329
403
|
}
|
|
@@ -338,8 +412,18 @@ class WebUsbTransport {
|
|
|
338
412
|
}
|
|
339
413
|
});
|
|
340
414
|
}
|
|
415
|
+
assertAcquired(path) {
|
|
416
|
+
if (!this.acquiredPaths.has(path)) {
|
|
417
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
341
420
|
post(session, name, data) {
|
|
342
421
|
return __awaiter(this, void 0, void 0, function* () {
|
|
422
|
+
this.assertAcquired(session);
|
|
423
|
+
if (this.deviceProtocol.get(session) === 'V2') {
|
|
424
|
+
yield this.sendProtocolV2UsbFlowControl(session, name, data);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
343
427
|
yield this.call(session, name, data);
|
|
344
428
|
});
|
|
345
429
|
}
|
|
@@ -368,6 +452,7 @@ class WebUsbTransport {
|
|
|
368
452
|
var _a;
|
|
369
453
|
return __awaiter(this, void 0, void 0, function* () {
|
|
370
454
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
455
|
+
this.staleProtocolPaths.add(path);
|
|
371
456
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
372
457
|
try {
|
|
373
458
|
const currentDevice = yield this.findDevice(path);
|
|
@@ -485,34 +570,33 @@ class WebUsbTransport {
|
|
|
485
570
|
throw lastError;
|
|
486
571
|
});
|
|
487
572
|
}
|
|
488
|
-
|
|
489
|
-
var _a
|
|
573
|
+
transferInOnce(path, length) {
|
|
574
|
+
var _a;
|
|
490
575
|
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);
|
|
576
|
+
const device = yield this.findDevice(path);
|
|
577
|
+
if (!device.opened) {
|
|
578
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
510
579
|
}
|
|
580
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
581
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
582
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
583
|
+
return this.getTransferInData(result);
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
closeConnectionAfterProbe(path) {
|
|
587
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
588
|
+
yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
589
|
+
yield this.closeOpenDevice(path);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
resetConnectionAfterProbe(path) {
|
|
593
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
594
|
+
yield this.closeConnectionAfterProbe(path);
|
|
511
595
|
yield this.getConnectedDevices();
|
|
512
596
|
yield this.connect(path, false);
|
|
513
597
|
});
|
|
514
598
|
}
|
|
515
|
-
withProtocolReadTimeout(
|
|
599
|
+
withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
|
|
516
600
|
return __awaiter(this, void 0, void 0, function* () {
|
|
517
601
|
let timer;
|
|
518
602
|
let timedOut = false;
|
|
@@ -527,11 +611,19 @@ class WebUsbTransport {
|
|
|
527
611
|
return yield Promise.race([
|
|
528
612
|
guardedPromise,
|
|
529
613
|
new Promise((_, reject) => {
|
|
530
|
-
timer = setTimeout(() => {
|
|
614
|
+
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
531
615
|
timedOut = true;
|
|
532
616
|
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
617
|
+
if (protocol === 'V1') {
|
|
618
|
+
try {
|
|
619
|
+
yield this.resetConnectionAfterProbe(path);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
533
625
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
534
|
-
}, timeoutMs);
|
|
626
|
+
}), timeoutMs);
|
|
535
627
|
}),
|
|
536
628
|
]);
|
|
537
629
|
}
|
|
@@ -547,7 +639,9 @@ class WebUsbTransport {
|
|
|
547
639
|
return false;
|
|
548
640
|
}
|
|
549
641
|
try {
|
|
550
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
642
|
+
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
643
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
644
|
+
});
|
|
551
645
|
return true;
|
|
552
646
|
}
|
|
553
647
|
catch (_error) {
|
|
@@ -562,10 +656,9 @@ class WebUsbTransport {
|
|
|
562
656
|
}
|
|
563
657
|
return transport.probeProtocolV2({
|
|
564
658
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
565
|
-
timeoutMs:
|
|
659
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
566
660
|
logger: this.Log,
|
|
567
661
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
568
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
569
662
|
});
|
|
570
663
|
});
|
|
571
664
|
}
|
|
@@ -574,6 +667,7 @@ class WebUsbTransport {
|
|
|
574
667
|
if (this.messages == null) {
|
|
575
668
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
576
669
|
}
|
|
670
|
+
this.assertAcquired(path);
|
|
577
671
|
const device = yield this.findDevice(path);
|
|
578
672
|
if (!device) {
|
|
579
673
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
@@ -582,9 +676,6 @@ class WebUsbTransport {
|
|
|
582
676
|
if (!protocol) {
|
|
583
677
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
584
678
|
}
|
|
585
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
586
|
-
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
587
|
-
}
|
|
588
679
|
if (protocol === 'V2') {
|
|
589
680
|
return this.callProtocolV2(path, name, data, options);
|
|
590
681
|
}
|
|
@@ -613,79 +704,8 @@ class WebUsbTransport {
|
|
|
613
704
|
});
|
|
614
705
|
}
|
|
615
706
|
callProtocolV2(path, name, data, options) {
|
|
616
|
-
var _a;
|
|
617
707
|
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;
|
|
708
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
689
709
|
});
|
|
690
710
|
}
|
|
691
711
|
receiveData(path, timeoutMs) {
|
|
@@ -726,45 +746,81 @@ class WebUsbTransport {
|
|
|
726
746
|
});
|
|
727
747
|
}
|
|
728
748
|
release(path) {
|
|
729
|
-
var _a, _b;
|
|
730
749
|
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);
|
|
750
|
+
this.acquiredPaths.delete(path);
|
|
751
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
752
|
+
yield this.closeOpenDevice(path);
|
|
742
753
|
this.deviceEndpoints.delete(path);
|
|
743
754
|
});
|
|
744
755
|
}
|
|
756
|
+
getProtocolV2UsbSchemas() {
|
|
757
|
+
if (!this.messages || !this.messagesV2) {
|
|
758
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
protocolV1: this.messages,
|
|
762
|
+
protocolV2: this.messagesV2,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
getProtocolV2UsbLogger() {
|
|
766
|
+
return this.Log;
|
|
767
|
+
}
|
|
768
|
+
writeProtocolV2UsbPacket(path, frame, _context) {
|
|
769
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
770
|
+
yield this.transferOutOnce(path, frame);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
readProtocolV2UsbPacket(path, _context) {
|
|
774
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
775
|
+
const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
776
|
+
return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
resetProtocolV2UsbNativeLink(path, _reason) {
|
|
780
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
781
|
+
yield this.closeOpenDevice(path);
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
onProtocolV2UsbLinkInvalidated(path, reason) {
|
|
785
|
+
var _a;
|
|
786
|
+
this.deviceProtocol.delete(path);
|
|
787
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
788
|
+
}
|
|
789
|
+
createProtocolV2UsbTimeoutError(name, timeoutMs) {
|
|
790
|
+
return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
|
|
791
|
+
}
|
|
745
792
|
getProtocolType(path) {
|
|
746
793
|
return this.deviceProtocol.get(path);
|
|
747
794
|
}
|
|
748
795
|
}
|
|
749
796
|
|
|
750
|
-
const
|
|
751
|
-
function
|
|
752
|
-
|
|
797
|
+
const BLE_ATT_HEADER_BYTES = 3;
|
|
798
|
+
function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
|
|
799
|
+
if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
|
|
800
|
+
return fallbackPacketCapacity;
|
|
801
|
+
}
|
|
802
|
+
return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
|
|
753
803
|
}
|
|
804
|
+
|
|
805
|
+
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
754
806
|
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
755
|
-
const
|
|
807
|
+
const BLE_PACKET_SIZE_FALLBACK = 192;
|
|
808
|
+
const BLE_PACKET_SIZE_MAXIMUM = 244;
|
|
756
809
|
const BLE_WRITE_DELAY_MS = 5;
|
|
757
|
-
const
|
|
758
|
-
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
810
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
|
|
759
811
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
760
812
|
class ElectronBleTransport {
|
|
761
813
|
constructor() {
|
|
762
814
|
this.name = 'ElectronBleTransport';
|
|
763
815
|
this.configured = false;
|
|
764
816
|
this.runPromise = null;
|
|
817
|
+
this.runPromiseDeviceId = null;
|
|
765
818
|
this.connectedDevices = new Set();
|
|
766
819
|
this.deviceProtocol = new Map();
|
|
767
820
|
this.deviceProtocolHints = new Map();
|
|
821
|
+
this.confirmedProtocolV2 = new Set();
|
|
822
|
+
this.deviceMtus = new Map();
|
|
823
|
+
this.devicePacketCapacities = new Map();
|
|
768
824
|
this.v1Buffers = new Map();
|
|
769
825
|
this.v2Assemblers = new Map();
|
|
770
826
|
this.v2FrameQueues = new Map();
|
|
@@ -786,16 +842,36 @@ class ElectronBleTransport {
|
|
|
786
842
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
787
843
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
|
|
788
844
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
789
|
-
yield this.
|
|
845
|
+
yield this.releaseNative(uuid);
|
|
790
846
|
}
|
|
791
847
|
}),
|
|
792
848
|
});
|
|
849
|
+
this.warnedMissingRelease = false;
|
|
793
850
|
this.notificationCleanups = new Map();
|
|
794
|
-
this.
|
|
851
|
+
this.mtuCleanups = new Map();
|
|
795
852
|
this.notificationTokens = new Map();
|
|
796
853
|
this.nextNotificationToken = 1;
|
|
797
854
|
}
|
|
855
|
+
toStaleBondError(error) {
|
|
856
|
+
var _a;
|
|
857
|
+
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
858
|
+
return error;
|
|
859
|
+
}
|
|
860
|
+
const errorMessage = error && typeof error === 'object' && 'message' in error
|
|
861
|
+
? String((_a = error.message) !== null && _a !== void 0 ? _a : '')
|
|
862
|
+
: String(error !== null && error !== void 0 ? error : '');
|
|
863
|
+
if (!hdShared.isBleStaleBondErrorText(errorMessage)) {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
return hdShared.ERRORS.TypedError(errorMessage.includes('Peer removed pairing information')
|
|
867
|
+
? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
868
|
+
: hdShared.HardwareErrorCode.BleDeviceBondError, errorMessage);
|
|
869
|
+
}
|
|
798
870
|
handleBluetoothError(error) {
|
|
871
|
+
const staleBondError = this.toStaleBondError(error);
|
|
872
|
+
if (staleBondError) {
|
|
873
|
+
throw staleBondError;
|
|
874
|
+
}
|
|
799
875
|
if (error && typeof error === 'object') {
|
|
800
876
|
if ('code' in error) {
|
|
801
877
|
if (error.code === hdShared.HardwareErrorCode.BlePoweredOff) {
|
|
@@ -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,69 @@ 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());
|
|
935
|
-
|
|
1043
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
|
|
1044
|
+
try {
|
|
1045
|
+
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
1046
|
+
}
|
|
1047
|
+
catch (error) {
|
|
1048
|
+
this.handleBluetoothError(error);
|
|
1049
|
+
}
|
|
1050
|
+
yield this.refreshBlePacketCapacity(uuid);
|
|
936
1051
|
const cleanup = this.createNotificationSubscription(uuid);
|
|
937
1052
|
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
1053
|
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
951
|
-
(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1054
|
+
if (protocolType === 'V2') {
|
|
1055
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[Electron BLE] Protocol V2 write configured', {
|
|
1056
|
+
writeMode: 'withoutResponse',
|
|
1057
|
+
negotiatedMtu: this.deviceMtus.get(uuid),
|
|
1058
|
+
packetCapacity: (_d = this.devicePacketCapacities.get(uuid)) !== null && _d !== void 0 ? _d : BLE_PACKET_SIZE_FALLBACK,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
956
1061
|
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
957
1062
|
}
|
|
958
1063
|
catch (error) {
|
|
959
|
-
(
|
|
1064
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.error('[Electron BLE] acquire failed:', error);
|
|
960
1065
|
try {
|
|
961
|
-
if (((
|
|
1066
|
+
if (((_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
962
1067
|
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
963
1068
|
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
964
1069
|
}
|
|
965
1070
|
}
|
|
966
1071
|
catch (cleanupError) {
|
|
967
|
-
(
|
|
1072
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
968
1073
|
}
|
|
969
1074
|
this.cleanupDeviceState(uuid);
|
|
970
1075
|
throw error;
|
|
971
1076
|
}
|
|
972
1077
|
});
|
|
973
1078
|
}
|
|
974
|
-
release(id) {
|
|
975
|
-
var _a
|
|
1079
|
+
release(id, _onclose, keepSession) {
|
|
1080
|
+
var _a;
|
|
976
1081
|
return __awaiter(this, void 0, void 0, function* () {
|
|
977
1082
|
try {
|
|
978
1083
|
yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
|
|
1084
|
+
yield this.releaseLogical(id, keepSession);
|
|
1085
|
+
}
|
|
1086
|
+
catch (error) {
|
|
1087
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
|
|
1088
|
+
this.cleanupDeviceState(id);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
disconnect(id) {
|
|
1093
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1094
|
+
return this.releaseNative(id);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
releaseNative(id) {
|
|
1098
|
+
var _a, _b;
|
|
1099
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1100
|
+
try {
|
|
979
1101
|
if (this.connectedDevices.has(id)) {
|
|
980
1102
|
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
981
1103
|
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
@@ -990,11 +1112,36 @@ class ElectronBleTransport {
|
|
|
990
1112
|
}
|
|
991
1113
|
});
|
|
992
1114
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1115
|
+
releaseLogical(id, keepSession) {
|
|
1116
|
+
var _a, _b, _c, _d;
|
|
1117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1118
|
+
try {
|
|
1119
|
+
if (!this.connectedDevices.has(id))
|
|
1120
|
+
return;
|
|
1121
|
+
this.cleanupDeviceState(id);
|
|
1122
|
+
const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
|
|
1123
|
+
if (!release) {
|
|
1124
|
+
if (!this.warnedMissingRelease) {
|
|
1125
|
+
this.warnedMissingRelease = true;
|
|
1126
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
|
|
1127
|
+
'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
yield release(id, keepSession);
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
|
|
1135
|
+
this.cleanupDeviceState(id);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
createProtocolMismatchError(expected, uuid) {
|
|
1140
|
+
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
1141
|
+
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
1142
|
}
|
|
996
1143
|
createProtocolDetectionError() {
|
|
997
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1144
|
+
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
1145
|
}
|
|
999
1146
|
clearProbeProtocol(uuid, protocol) {
|
|
1000
1147
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1010,12 +1157,16 @@ class ElectronBleTransport {
|
|
|
1010
1157
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
1011
1158
|
return 'V1';
|
|
1012
1159
|
}
|
|
1013
|
-
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1160
|
+
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
1014
1161
|
}
|
|
1015
1162
|
if (expectedProtocol === 'V2') {
|
|
1016
|
-
this.
|
|
1017
|
-
|
|
1018
|
-
|
|
1163
|
+
if (yield this.probeProtocolV2(uuid)) {
|
|
1164
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1165
|
+
this.confirmedProtocolV2.add(uuid);
|
|
1166
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1167
|
+
return 'V2';
|
|
1168
|
+
}
|
|
1169
|
+
throw this.createProtocolMismatchError(expectedProtocol, uuid);
|
|
1019
1170
|
}
|
|
1020
1171
|
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1021
1172
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
@@ -1026,6 +1177,9 @@ class ElectronBleTransport {
|
|
|
1026
1177
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1027
1178
|
if (detected) {
|
|
1028
1179
|
this.deviceProtocol.set(uuid, protocol);
|
|
1180
|
+
if (protocol === 'V2') {
|
|
1181
|
+
this.confirmedProtocolV2.add(uuid);
|
|
1182
|
+
}
|
|
1029
1183
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1030
1184
|
return protocol;
|
|
1031
1185
|
}
|
|
@@ -1049,38 +1203,22 @@ class ElectronBleTransport {
|
|
|
1049
1203
|
});
|
|
1050
1204
|
}
|
|
1051
1205
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1052
|
-
var _a
|
|
1206
|
+
var _a;
|
|
1053
1207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1054
1208
|
yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
|
|
1055
1209
|
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1056
1210
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1057
1211
|
this.resetProtocolV2Frames(uuid);
|
|
1058
|
-
if (this.runPromise) {
|
|
1212
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1059
1213
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1060
1214
|
this.runPromise.reject(error);
|
|
1061
1215
|
this.runPromise = null;
|
|
1216
|
+
this.runPromiseDeviceId = null;
|
|
1062
1217
|
}
|
|
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;
|
|
1218
|
+
if (!this.notificationCleanups.has(uuid)) {
|
|
1219
|
+
const cleanup = this.createNotificationSubscription(uuid);
|
|
1220
|
+
this.notificationCleanups.set(uuid, cleanup);
|
|
1081
1221
|
}
|
|
1082
|
-
const cleanup = this.createNotificationSubscription(uuid);
|
|
1083
|
-
this.notificationCleanups.set(uuid, cleanup);
|
|
1084
1222
|
});
|
|
1085
1223
|
}
|
|
1086
1224
|
probeProtocolV1(uuid) {
|
|
@@ -1091,12 +1229,15 @@ class ElectronBleTransport {
|
|
|
1091
1229
|
}
|
|
1092
1230
|
try {
|
|
1093
1231
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1094
|
-
yield this.callProtocolV1(uuid, '
|
|
1232
|
+
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1095
1233
|
return true;
|
|
1096
1234
|
}
|
|
1097
1235
|
catch (error) {
|
|
1098
1236
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1099
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1
|
|
1237
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
|
|
1238
|
+
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
1239
|
+
throw error;
|
|
1240
|
+
}
|
|
1100
1241
|
return false;
|
|
1101
1242
|
}
|
|
1102
1243
|
});
|
|
@@ -1119,6 +1260,7 @@ class ElectronBleTransport {
|
|
|
1119
1260
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1120
1261
|
this.resetProtocolV2Frames(uuid);
|
|
1121
1262
|
},
|
|
1263
|
+
shouldRethrow: hdShared.isBleStaleBondHardwareError,
|
|
1122
1264
|
});
|
|
1123
1265
|
if (!detected) {
|
|
1124
1266
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1126,33 +1268,67 @@ class ElectronBleTransport {
|
|
|
1126
1268
|
return detected;
|
|
1127
1269
|
});
|
|
1128
1270
|
}
|
|
1129
|
-
|
|
1271
|
+
writeOnce(uuid, hexData) {
|
|
1272
|
+
var _a;
|
|
1130
1273
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1131
|
-
const
|
|
1132
|
-
if (
|
|
1133
|
-
|
|
1134
|
-
yield this.writeOnce(uuid, hexData);
|
|
1135
|
-
return;
|
|
1274
|
+
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1275
|
+
if (!nobleBle) {
|
|
1276
|
+
throw new Error('Noble BLE API not available');
|
|
1136
1277
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (
|
|
1143
|
-
|
|
1278
|
+
try {
|
|
1279
|
+
yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
const staleBondError = this.toStaleBondError(error);
|
|
1283
|
+
if (staleBondError) {
|
|
1284
|
+
throw staleBondError;
|
|
1144
1285
|
}
|
|
1286
|
+
throw error;
|
|
1145
1287
|
}
|
|
1146
1288
|
});
|
|
1147
1289
|
}
|
|
1148
|
-
|
|
1149
|
-
var _a;
|
|
1290
|
+
refreshBlePacketCapacity(uuid) {
|
|
1291
|
+
var _a, _b;
|
|
1150
1292
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1293
|
+
const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
|
|
1294
|
+
this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
updateBlePacketCapacity(uuid, mtu) {
|
|
1298
|
+
const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
|
|
1299
|
+
if (typeof mtu === 'number') {
|
|
1300
|
+
this.deviceMtus.set(uuid, mtu);
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
this.deviceMtus.delete(uuid);
|
|
1304
|
+
}
|
|
1305
|
+
this.devicePacketCapacities.set(uuid, packetCapacity);
|
|
1306
|
+
}
|
|
1307
|
+
createMtuSubscription(uuid) {
|
|
1308
|
+
var _a, _b;
|
|
1309
|
+
const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
|
|
1310
|
+
if (!onMtuChanged)
|
|
1311
|
+
return undefined;
|
|
1312
|
+
return onMtuChanged(device => {
|
|
1313
|
+
if (device.id === uuid) {
|
|
1314
|
+
this.updateBlePacketCapacity(uuid, device.mtu);
|
|
1154
1315
|
}
|
|
1155
|
-
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
|
|
1319
|
+
var _a;
|
|
1320
|
+
const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
|
|
1321
|
+
const shouldPace = !context.highThroughput;
|
|
1322
|
+
return transport.writeProtocolV2BleFrame({
|
|
1323
|
+
frame,
|
|
1324
|
+
packetCapacity,
|
|
1325
|
+
assertActive: assertCurrentGeneration,
|
|
1326
|
+
signal: context.signal,
|
|
1327
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1328
|
+
initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
|
|
1329
|
+
burstSize: shouldPace ? 1 : undefined,
|
|
1330
|
+
burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
|
|
1331
|
+
writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
|
|
1156
1332
|
});
|
|
1157
1333
|
}
|
|
1158
1334
|
handleNotification(deviceId, hexData) {
|
|
@@ -1163,7 +1339,7 @@ class ElectronBleTransport {
|
|
|
1163
1339
|
if (this.deviceProtocol.get(deviceId) === 'V2') {
|
|
1164
1340
|
this.rejectProtocolV2Frames(deviceId, error);
|
|
1165
1341
|
}
|
|
1166
|
-
else if (this.runPromise) {
|
|
1342
|
+
else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1167
1343
|
this.runPromise.reject(error);
|
|
1168
1344
|
}
|
|
1169
1345
|
return;
|
|
@@ -1215,16 +1391,8 @@ class ElectronBleTransport {
|
|
|
1215
1391
|
}
|
|
1216
1392
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1217
1393
|
}
|
|
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
1394
|
resetProtocolV2Frames(uuid) {
|
|
1226
|
-
this.
|
|
1227
|
-
this.v2FramePromises.delete(uuid);
|
|
1395
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1228
1396
|
}
|
|
1229
1397
|
rejectProtocolV2Frames(uuid, error) {
|
|
1230
1398
|
this.v2FrameQueues.delete(uuid);
|
|
@@ -1257,17 +1425,19 @@ class ElectronBleTransport {
|
|
|
1257
1425
|
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1258
1426
|
if (result.error) {
|
|
1259
1427
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
1260
|
-
if (this.runPromise) {
|
|
1428
|
+
if (this.runPromise && this.runPromiseDeviceId === deviceId) {
|
|
1261
1429
|
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
1262
1430
|
}
|
|
1263
1431
|
return;
|
|
1264
1432
|
}
|
|
1265
|
-
if (result.isComplete &&
|
|
1433
|
+
if (result.isComplete &&
|
|
1434
|
+
result.completePacket &&
|
|
1435
|
+
this.runPromise &&
|
|
1436
|
+
this.runPromiseDeviceId === deviceId) {
|
|
1266
1437
|
this.runPromise.resolve(result.completePacket);
|
|
1267
1438
|
}
|
|
1268
1439
|
}
|
|
1269
1440
|
call(uuid, name, data, options) {
|
|
1270
|
-
var _a;
|
|
1271
1441
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1272
1442
|
if (!this._messages) {
|
|
1273
1443
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -1279,15 +1449,21 @@ class ElectronBleTransport {
|
|
|
1279
1449
|
if (!protocol) {
|
|
1280
1450
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
1281
1451
|
}
|
|
1282
|
-
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
1283
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
|
|
1284
|
-
}
|
|
1285
1452
|
if (protocol === 'V2') {
|
|
1286
1453
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1287
1454
|
}
|
|
1288
1455
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1289
1456
|
});
|
|
1290
1457
|
}
|
|
1458
|
+
post(uuid, name, data) {
|
|
1459
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1460
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1461
|
+
yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
yield this.callProtocolV1(uuid, name, data);
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1291
1467
|
callProtocolV1(uuid, name, data, options) {
|
|
1292
1468
|
var _a, _b;
|
|
1293
1469
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1301,6 +1477,7 @@ class ElectronBleTransport {
|
|
|
1301
1477
|
const runPromise = hdShared.createDeferred();
|
|
1302
1478
|
runPromise.promise.catch(() => undefined);
|
|
1303
1479
|
this.runPromise = runPromise;
|
|
1480
|
+
this.runPromiseDeviceId = uuid;
|
|
1304
1481
|
const messages = this._messages;
|
|
1305
1482
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1306
1483
|
let timeout;
|
|
@@ -1317,7 +1494,16 @@ class ElectronBleTransport {
|
|
|
1317
1494
|
if (hexString.length === 0) {
|
|
1318
1495
|
throw new Error(`Buffer ${i + 1} is empty`);
|
|
1319
1496
|
}
|
|
1320
|
-
|
|
1497
|
+
try {
|
|
1498
|
+
yield window.desktopApi.nobleBle.write(uuid, hexString);
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
const staleBondError = this.toStaleBondError(error);
|
|
1502
|
+
if (staleBondError) {
|
|
1503
|
+
throw staleBondError;
|
|
1504
|
+
}
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1321
1507
|
}
|
|
1322
1508
|
const response = yield Promise.race([
|
|
1323
1509
|
runPromise.promise,
|
|
@@ -1339,6 +1525,17 @@ class ElectronBleTransport {
|
|
|
1339
1525
|
}
|
|
1340
1526
|
catch (e) {
|
|
1341
1527
|
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
1528
|
+
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1529
|
+
if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1530
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1531
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1532
|
+
notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
|
|
1533
|
+
this.notificationCleanups.delete(uuid);
|
|
1534
|
+
this.notificationTokens.delete(uuid);
|
|
1535
|
+
if (!isProbeTimeout) {
|
|
1536
|
+
yield this.releaseNative(uuid);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1342
1539
|
throw e;
|
|
1343
1540
|
}
|
|
1344
1541
|
finally {
|
|
@@ -1346,22 +1543,23 @@ class ElectronBleTransport {
|
|
|
1346
1543
|
clearTimeout(timeout);
|
|
1347
1544
|
if (this.runPromise === runPromise) {
|
|
1348
1545
|
this.runPromise = null;
|
|
1546
|
+
this.runPromiseDeviceId = null;
|
|
1349
1547
|
}
|
|
1350
1548
|
}
|
|
1351
1549
|
});
|
|
1352
1550
|
}
|
|
1353
1551
|
callProtocolV2(uuid, name, data, options) {
|
|
1354
|
-
var _a
|
|
1552
|
+
var _a;
|
|
1355
1553
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1356
1554
|
if (!this._messages || !this._messagesV2) {
|
|
1357
1555
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1358
1556
|
}
|
|
1359
|
-
const callOptions =
|
|
1557
|
+
const callOptions = options;
|
|
1360
1558
|
try {
|
|
1361
1559
|
return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
|
|
1362
1560
|
}
|
|
1363
1561
|
catch (e) {
|
|
1364
|
-
(
|
|
1562
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1365
1563
|
throw e;
|
|
1366
1564
|
}
|
|
1367
1565
|
});
|
|
@@ -1376,6 +1574,7 @@ class ElectronBleTransport {
|
|
|
1376
1574
|
};
|
|
1377
1575
|
return {
|
|
1378
1576
|
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1577
|
+
maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1379
1578
|
generation,
|
|
1380
1579
|
prepareCall: () => {
|
|
1381
1580
|
var _a;
|
|
@@ -1383,10 +1582,7 @@ class ElectronBleTransport {
|
|
|
1383
1582
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1384
1583
|
this.resetProtocolV2Frames(uuid);
|
|
1385
1584
|
},
|
|
1386
|
-
writeFrame: (frame) =>
|
|
1387
|
-
assertCurrentGeneration();
|
|
1388
|
-
return this.writeWithChunking(uuid, transport.bytesToHex(frame));
|
|
1389
|
-
},
|
|
1585
|
+
writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
|
|
1390
1586
|
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1391
1587
|
assertCurrentGeneration();
|
|
1392
1588
|
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
@@ -1406,6 +1602,7 @@ class ElectronBleTransport {
|
|
|
1406
1602
|
};
|
|
1407
1603
|
}
|
|
1408
1604
|
processProtocolV1Notification(deviceId, hexData) {
|
|
1605
|
+
var _a;
|
|
1409
1606
|
try {
|
|
1410
1607
|
if (typeof hexData !== 'string') {
|
|
1411
1608
|
return { isComplete: false, error: 'Invalid hexData type' };
|
|
@@ -1424,6 +1621,10 @@ class ElectronBleTransport {
|
|
|
1424
1621
|
bufferState.buffer = [...data.subarray(3)];
|
|
1425
1622
|
}
|
|
1426
1623
|
else {
|
|
1624
|
+
if (bufferState.buffer.length === 0) {
|
|
1625
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
|
|
1626
|
+
return { isComplete: false };
|
|
1627
|
+
}
|
|
1427
1628
|
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
1428
1629
|
}
|
|
1429
1630
|
if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {
|