@onekeyfe/hd-transport-web-device 1.1.27 → 1.2.0-alpha.0
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 +231 -0
- package/dist/electron-ble-transport.d.ts +50 -13
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +80 -17
- package/dist/index.js +927 -115
- package/dist/webusb.d.ts +29 -3
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +612 -133
- package/src/webusb.ts +595 -47
package/dist/index.js
CHANGED
|
@@ -41,16 +41,43 @@ 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 { parseConfigure: parseConfigure$1,
|
|
44
|
+
const { parseConfigure: parseConfigure$1, check: check$1, ProtocolV1: ProtocolV1$1 } = transport__default["default"];
|
|
45
45
|
const CONFIGURATION_ID = 1;
|
|
46
46
|
const INTERFACE_ID = 0;
|
|
47
47
|
const ENDPOINT_ID = 1;
|
|
48
|
-
const PACKET_SIZE =
|
|
49
|
-
const
|
|
48
|
+
const PACKET_SIZE = transport.PROTOCOL_V1_USB_PACKET_SIZE;
|
|
49
|
+
const PAYLOAD_SIZE = transport.PROTOCOL_V1_CHUNK_PAYLOAD_SIZE;
|
|
50
|
+
const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
51
|
+
const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
50
52
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
51
53
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
54
|
+
const PROTOCOL_PROBE_TIMEOUT = 1000;
|
|
55
|
+
const WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
|
|
56
|
+
function shouldSuppressWebUsbCallLog(name) {
|
|
57
|
+
const normalized = name.replace(/[_\s-]/g, '');
|
|
58
|
+
return WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
59
|
+
}
|
|
60
|
+
function isLogBlockCommand$1(name) {
|
|
61
|
+
var _a, _b;
|
|
62
|
+
return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
|
|
63
|
+
}
|
|
64
|
+
function shouldBlockWebUsbCallDataLog(name) {
|
|
65
|
+
const normalized = name.replace(/[_\s-]/g, '');
|
|
66
|
+
return isLogBlockCommand$1(name) || WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
67
|
+
}
|
|
68
|
+
function inferProtocolHintFromDeviceName$1(name) {
|
|
69
|
+
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
70
|
+
}
|
|
52
71
|
class WebUsbTransport {
|
|
53
72
|
constructor() {
|
|
73
|
+
this.deviceProtocol = new Map();
|
|
74
|
+
this.deviceProtocolHints = new Map();
|
|
75
|
+
this.protocolV2Assemblers = new Map();
|
|
76
|
+
this.protocolV2Sessions = new Map();
|
|
77
|
+
this.protocolV2ReadTimeouts = new Map();
|
|
78
|
+
this.deviceEndpoints = new Map();
|
|
79
|
+
this.mockSerialPaths = new WeakMap();
|
|
80
|
+
this.mockSerialCounter = 0;
|
|
54
81
|
this.name = 'WebUsbTransport';
|
|
55
82
|
this.stopped = false;
|
|
56
83
|
this.configured = false;
|
|
@@ -72,6 +99,13 @@ class WebUsbTransport {
|
|
|
72
99
|
this.configured = true;
|
|
73
100
|
this.messages = messages;
|
|
74
101
|
}
|
|
102
|
+
configureProtocolV2(signedData) {
|
|
103
|
+
var _a;
|
|
104
|
+
this.messagesV2 = parseConfigure$1(signedData);
|
|
105
|
+
this.protocolV2Sessions.clear();
|
|
106
|
+
this.protocolV2ReadTimeouts.clear();
|
|
107
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] Protocol V2 schema configured');
|
|
108
|
+
}
|
|
75
109
|
promptDeviceAccess() {
|
|
76
110
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
111
|
if (!this.usb)
|
|
@@ -92,31 +126,61 @@ class WebUsbTransport {
|
|
|
92
126
|
return this.deviceList;
|
|
93
127
|
});
|
|
94
128
|
}
|
|
129
|
+
getDevicePath(device) {
|
|
130
|
+
var _a;
|
|
131
|
+
if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
|
|
132
|
+
return device.serialNumber;
|
|
133
|
+
}
|
|
134
|
+
let path = this.mockSerialPaths.get(device);
|
|
135
|
+
if (!path) {
|
|
136
|
+
this.mockSerialCounter += 1;
|
|
137
|
+
path = `mock-serial:${device.vendorId.toString(16)}:${device.productId.toString(16)}:${this.mockSerialCounter}`;
|
|
138
|
+
this.mockSerialPaths.set(device, path);
|
|
139
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUSB] device has no serial number, using mock path: ${path}`);
|
|
140
|
+
}
|
|
141
|
+
return path;
|
|
142
|
+
}
|
|
95
143
|
getConnectedDevices() {
|
|
96
144
|
return __awaiter(this, void 0, void 0, function* () {
|
|
97
145
|
if (!this.usb)
|
|
98
146
|
return [];
|
|
99
147
|
const devices = yield this.usb.getDevices();
|
|
100
|
-
const onekeyDevices = devices.filter(dev =>
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
148
|
+
const onekeyDevices = devices.filter(dev => hdShared.ONEKEY_WEBUSB_FILTER.some(desc => dev.vendorId === desc.vendorId && dev.productId === desc.productId));
|
|
149
|
+
this.deviceList = onekeyDevices.map(device => {
|
|
150
|
+
const path = this.getDevicePath(device);
|
|
151
|
+
const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
|
|
152
|
+
if (protocolHint) {
|
|
153
|
+
this.deviceProtocolHints.set(path, protocolHint);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
path,
|
|
157
|
+
device,
|
|
158
|
+
commType: 'webusb',
|
|
159
|
+
};
|
|
104
160
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}));
|
|
161
|
+
for (const dev of onekeyDevices) {
|
|
162
|
+
this.Log.debug(`[WebUSB] Device: name="${dev.productName}" serial="${dev.serialNumber}" ` +
|
|
163
|
+
`VID=0x${dev.vendorId.toString(16)} PID=0x${dev.productId.toString(16)}`);
|
|
164
|
+
}
|
|
110
165
|
return this.deviceList;
|
|
111
166
|
});
|
|
112
167
|
}
|
|
113
168
|
acquire(input) {
|
|
114
|
-
var _a;
|
|
169
|
+
var _a, _b, _c;
|
|
115
170
|
return __awaiter(this, void 0, void 0, function* () {
|
|
116
171
|
if (!input.path)
|
|
117
172
|
return;
|
|
118
173
|
try {
|
|
174
|
+
yield this.closeOpenDevice(input.path);
|
|
119
175
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
176
|
+
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
177
|
+
const protocolHint = input.expectedProtocol
|
|
178
|
+
? undefined
|
|
179
|
+
: (_c = this.deviceProtocolHints.get(input.path)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName$1(deviceName);
|
|
180
|
+
if (protocolHint) {
|
|
181
|
+
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
182
|
+
}
|
|
183
|
+
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
120
184
|
return yield Promise.resolve(input.path);
|
|
121
185
|
}
|
|
122
186
|
catch (e) {
|
|
@@ -125,6 +189,40 @@ class WebUsbTransport {
|
|
|
125
189
|
}
|
|
126
190
|
});
|
|
127
191
|
}
|
|
192
|
+
createProtocolMismatchError(expected) {
|
|
193
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
194
|
+
}
|
|
195
|
+
createProtocolDetectionError() {
|
|
196
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
|
|
197
|
+
}
|
|
198
|
+
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
+
if (expectedProtocol === 'V1') {
|
|
201
|
+
if (yield this.probeProtocolV1(path)) {
|
|
202
|
+
this.deviceProtocol.set(path, 'V1');
|
|
203
|
+
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
204
|
+
return 'V1';
|
|
205
|
+
}
|
|
206
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
207
|
+
}
|
|
208
|
+
if (expectedProtocol === 'V2') {
|
|
209
|
+
this.deviceProtocol.set(path, 'V2');
|
|
210
|
+
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
|
|
211
|
+
return 'V2';
|
|
212
|
+
}
|
|
213
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
214
|
+
for (const protocol of probeOrder) {
|
|
215
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
|
|
216
|
+
if (detected) {
|
|
217
|
+
this.deviceProtocol.set(path, protocol);
|
|
218
|
+
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
219
|
+
return protocol;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.deviceProtocol.delete(path);
|
|
223
|
+
throw this.createProtocolDetectionError();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
128
226
|
findDevice(path) {
|
|
129
227
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
228
|
if (this.deviceList.length === 0) {
|
|
@@ -157,19 +255,99 @@ class WebUsbTransport {
|
|
|
157
255
|
}
|
|
158
256
|
});
|
|
159
257
|
}
|
|
258
|
+
discoverEndpoints(device) {
|
|
259
|
+
var _a, _b;
|
|
260
|
+
for (const config of device.configurations) {
|
|
261
|
+
for (const iface of config.interfaces) {
|
|
262
|
+
for (const alt of iface.alternates) {
|
|
263
|
+
if (alt.interfaceClass === 0xff) {
|
|
264
|
+
let endpointIn = this.endpointId;
|
|
265
|
+
let endpointOut = this.endpointId;
|
|
266
|
+
for (const ep of alt.endpoints) {
|
|
267
|
+
if (ep.direction === 'in')
|
|
268
|
+
endpointIn = ep.endpointNumber;
|
|
269
|
+
else
|
|
270
|
+
endpointOut = ep.endpointNumber;
|
|
271
|
+
}
|
|
272
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] discovered vendor interface ${iface.interfaceNumber}, ` +
|
|
273
|
+
`endpointIn=${endpointIn}, endpointOut=${endpointOut}`);
|
|
274
|
+
return { interfaceNumber: iface.interfaceNumber, endpointIn, endpointOut };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[WebUsbTransport] no vendor interface found, using defaults');
|
|
280
|
+
return {
|
|
281
|
+
interfaceNumber: this.interfaceId,
|
|
282
|
+
endpointIn: this.endpointId,
|
|
283
|
+
endpointOut: this.endpointId,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
160
286
|
connectToDevice(path, first) {
|
|
287
|
+
var _a, _b;
|
|
161
288
|
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
289
|
+
let device = yield this.findDevice(path);
|
|
290
|
+
this.Log.debug('[WebUsbTransport] connecting to device:', device.productName, 'PID:', device.productId);
|
|
291
|
+
if (!device.opened) {
|
|
292
|
+
yield device.open();
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
yield device.reset();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
299
|
+
}
|
|
300
|
+
yield this.getConnectedDevices();
|
|
301
|
+
device = yield this.findDevice(path);
|
|
302
|
+
if (!device.opened) {
|
|
303
|
+
yield device.open();
|
|
304
|
+
}
|
|
305
|
+
if (first ||
|
|
306
|
+
!device.configuration ||
|
|
307
|
+
device.configuration.configurationValue !== this.configurationId) {
|
|
165
308
|
yield device.selectConfiguration(this.configurationId);
|
|
166
|
-
try {
|
|
167
|
-
yield device.reset();
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
}
|
|
171
309
|
}
|
|
172
|
-
|
|
310
|
+
const endpoints = this.discoverEndpoints(device);
|
|
311
|
+
this.deviceEndpoints.set(path, endpoints);
|
|
312
|
+
(_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
313
|
+
this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
314
|
+
yield device.claimInterface(endpoints.interfaceNumber);
|
|
315
|
+
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
316
|
+
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
closeOpenDevice(path) {
|
|
320
|
+
var _a, _b, _c, _d, _e;
|
|
321
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
322
|
+
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
323
|
+
const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
|
|
324
|
+
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
325
|
+
return;
|
|
326
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
327
|
+
const ifaceNum = (_c = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _c !== void 0 ? _c : this.interfaceId;
|
|
328
|
+
try {
|
|
329
|
+
yield current.releaseInterface(ifaceNum);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
yield current.close();
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
clearEndpointHalt(device, direction, endpointNumber) {
|
|
343
|
+
var _a;
|
|
344
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
345
|
+
try {
|
|
346
|
+
yield device.clearHalt(direction, endpointNumber);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
350
|
+
}
|
|
173
351
|
});
|
|
174
352
|
}
|
|
175
353
|
post(session, name, data) {
|
|
@@ -199,14 +377,17 @@ class WebUsbTransport {
|
|
|
199
377
|
message.includes('networkerror'));
|
|
200
378
|
}
|
|
201
379
|
reconnectForPacketIoRetry(path, direction, attempt, error) {
|
|
380
|
+
var _a;
|
|
202
381
|
return __awaiter(this, void 0, void 0, function* () {
|
|
203
382
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
204
383
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
205
384
|
try {
|
|
206
385
|
const currentDevice = yield this.findDevice(path);
|
|
207
386
|
if (currentDevice.opened) {
|
|
387
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
388
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
208
389
|
try {
|
|
209
|
-
yield currentDevice.releaseInterface(
|
|
390
|
+
yield currentDevice.releaseInterface(ifaceNum);
|
|
210
391
|
}
|
|
211
392
|
catch (releaseError) {
|
|
212
393
|
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
@@ -239,6 +420,7 @@ class WebUsbTransport {
|
|
|
239
420
|
return copied.buffer;
|
|
240
421
|
}
|
|
241
422
|
transferOutWithRetry(path, packet) {
|
|
423
|
+
var _a;
|
|
242
424
|
return __awaiter(this, void 0, void 0, function* () {
|
|
243
425
|
let lastError;
|
|
244
426
|
for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
|
|
@@ -247,8 +429,10 @@ class WebUsbTransport {
|
|
|
247
429
|
if (!device.opened) {
|
|
248
430
|
yield this.connect(path, false);
|
|
249
431
|
}
|
|
432
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
433
|
+
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
250
434
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
251
|
-
yield device.transferOut(
|
|
435
|
+
yield device.transferOut(endpointOut, transferBuffer);
|
|
252
436
|
return;
|
|
253
437
|
}
|
|
254
438
|
catch (error) {
|
|
@@ -269,20 +453,29 @@ class WebUsbTransport {
|
|
|
269
453
|
throw lastError;
|
|
270
454
|
});
|
|
271
455
|
}
|
|
272
|
-
transferInWithRetry(path, length) {
|
|
456
|
+
transferInWithRetry(path, length, cancelToken) {
|
|
457
|
+
var _a;
|
|
273
458
|
return __awaiter(this, void 0, void 0, function* () {
|
|
274
459
|
let lastError;
|
|
275
460
|
for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
|
|
461
|
+
if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.cancelled) {
|
|
462
|
+
throw new Error('transferIn cancelled');
|
|
463
|
+
}
|
|
276
464
|
try {
|
|
277
465
|
const device = yield this.findDevice(path);
|
|
278
466
|
if (!device.opened) {
|
|
279
467
|
yield this.connect(path, false);
|
|
280
468
|
}
|
|
281
|
-
const
|
|
469
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
470
|
+
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
471
|
+
const result = yield device.transferIn(endpointIn, length);
|
|
282
472
|
return this.getTransferInData(result);
|
|
283
473
|
}
|
|
284
474
|
catch (error) {
|
|
285
475
|
lastError = error;
|
|
476
|
+
if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.cancelled) {
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
286
479
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
287
480
|
if (!shouldRetry) {
|
|
288
481
|
throw error;
|
|
@@ -299,7 +492,92 @@ class WebUsbTransport {
|
|
|
299
492
|
throw lastError;
|
|
300
493
|
});
|
|
301
494
|
}
|
|
302
|
-
|
|
495
|
+
resetConnectionAfterProbe(path) {
|
|
496
|
+
var _a, _b;
|
|
497
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
498
|
+
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
499
|
+
this.protocolV2Sessions.delete(path);
|
|
500
|
+
this.protocolV2ReadTimeouts.delete(path);
|
|
501
|
+
try {
|
|
502
|
+
const device = yield this.findDevice(path);
|
|
503
|
+
if (device.opened) {
|
|
504
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
505
|
+
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
506
|
+
try {
|
|
507
|
+
yield device.releaseInterface(ifaceNum);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
511
|
+
}
|
|
512
|
+
yield device.close();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
517
|
+
}
|
|
518
|
+
yield this.getConnectedDevices();
|
|
519
|
+
yield this.connect(path, false);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
|
|
523
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
524
|
+
let timer;
|
|
525
|
+
let timedOut = false;
|
|
526
|
+
const waitForeverAfterTimeout = () => new Promise(() => { });
|
|
527
|
+
const guardedPromise = promise.then(value => (timedOut ? waitForeverAfterTimeout() : value), error => {
|
|
528
|
+
if (timedOut) {
|
|
529
|
+
return waitForeverAfterTimeout();
|
|
530
|
+
}
|
|
531
|
+
throw error;
|
|
532
|
+
});
|
|
533
|
+
try {
|
|
534
|
+
return yield Promise.race([
|
|
535
|
+
guardedPromise,
|
|
536
|
+
new Promise((_, reject) => {
|
|
537
|
+
timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
538
|
+
timedOut = true;
|
|
539
|
+
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
540
|
+
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
541
|
+
}), timeoutMs);
|
|
542
|
+
}),
|
|
543
|
+
]);
|
|
544
|
+
}
|
|
545
|
+
finally {
|
|
546
|
+
if (timer)
|
|
547
|
+
clearTimeout(timer);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
probeProtocolV1(path) {
|
|
552
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
553
|
+
if (!this.messages) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
probeProtocolV2(path) {
|
|
567
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
568
|
+
if (!this.messages || !this.messagesV2) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
return transport.probeProtocolV2({
|
|
572
|
+
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
573
|
+
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
574
|
+
logger: this.Log,
|
|
575
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
576
|
+
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
call(path, name, data, options) {
|
|
303
581
|
return __awaiter(this, void 0, void 0, function* () {
|
|
304
582
|
if (this.messages == null) {
|
|
305
583
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
@@ -308,33 +586,123 @@ class WebUsbTransport {
|
|
|
308
586
|
if (!device) {
|
|
309
587
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
310
588
|
}
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
313
|
-
|
|
589
|
+
const protocol = this.deviceProtocol.get(path);
|
|
590
|
+
if (!protocol) {
|
|
591
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
|
|
592
|
+
}
|
|
593
|
+
if (shouldSuppressWebUsbCallLog(name)) ;
|
|
594
|
+
else if (shouldBlockWebUsbCallDataLog(name)) {
|
|
595
|
+
this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
|
|
314
596
|
}
|
|
315
597
|
else {
|
|
316
|
-
this.Log.debug('call-', ' name: ', name, ' data: ', data);
|
|
598
|
+
this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
599
|
+
}
|
|
600
|
+
if (protocol === 'V2') {
|
|
601
|
+
return this.callProtocolV2(path, name, data, options);
|
|
602
|
+
}
|
|
603
|
+
return this.callProtocolV1(path, name, data, options);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
callProtocolV1(path, name, data, options) {
|
|
607
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
608
|
+
const { messages } = this;
|
|
609
|
+
if (!messages) {
|
|
610
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
317
611
|
}
|
|
318
|
-
const encodeBuffers =
|
|
612
|
+
const encodeBuffers = ProtocolV1$1.encodeMessageChunks(messages, name, data);
|
|
319
613
|
for (const buffer of encodeBuffers) {
|
|
320
614
|
const newArray = new Uint8Array(PACKET_SIZE);
|
|
321
|
-
newArray[0] =
|
|
615
|
+
newArray[0] = REPORT_ID;
|
|
322
616
|
newArray.set(new Uint8Array(buffer), 1);
|
|
323
617
|
yield this.transferOutWithRetry(path, newArray);
|
|
324
618
|
}
|
|
325
|
-
const resData = yield this.receiveData(path);
|
|
619
|
+
const resData = yield this.receiveData(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
326
620
|
if (typeof resData !== 'string') {
|
|
327
621
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
328
622
|
}
|
|
329
|
-
const jsonData =
|
|
623
|
+
const jsonData = ProtocolV1$1.decodeMessage(messages, resData);
|
|
330
624
|
return check$1.call(jsonData);
|
|
331
625
|
});
|
|
332
626
|
}
|
|
333
|
-
|
|
627
|
+
callProtocolV2(path, name, data, options) {
|
|
628
|
+
var _a;
|
|
334
629
|
return __awaiter(this, void 0, void 0, function* () {
|
|
335
|
-
const
|
|
630
|
+
const protocolV1Messages = this.messages;
|
|
631
|
+
if (!this.messagesV2) {
|
|
632
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
|
|
633
|
+
}
|
|
634
|
+
if (!protocolV1Messages) {
|
|
635
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
636
|
+
}
|
|
637
|
+
let session = this.protocolV2Sessions.get(path);
|
|
638
|
+
if (!session) {
|
|
639
|
+
session = new transport.ProtocolV2Session({
|
|
640
|
+
schemas: {
|
|
641
|
+
protocolV1: protocolV1Messages,
|
|
642
|
+
protocolV2: this.messagesV2,
|
|
643
|
+
},
|
|
644
|
+
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
645
|
+
writeFrame: (frame) => this.transferOutWithRetry(path, frame),
|
|
646
|
+
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
647
|
+
logger: this.Log,
|
|
648
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
649
|
+
createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
650
|
+
});
|
|
651
|
+
this.protocolV2Sessions.set(path, session);
|
|
652
|
+
}
|
|
653
|
+
this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
|
|
654
|
+
(_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
655
|
+
try {
|
|
656
|
+
return yield session.call(name, data, options);
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
this.protocolV2ReadTimeouts.delete(path);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
receiveProtocolV2Frame(path, timeoutMs) {
|
|
664
|
+
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;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
receiveData(path, timeoutMs) {
|
|
692
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
693
|
+
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
694
|
+
const readPacket = () => __awaiter(this, void 0, void 0, function* () {
|
|
695
|
+
const cancelToken = { cancelled: false };
|
|
696
|
+
const transferIn = this.transferInWithRetry(path, PACKET_SIZE, cancelToken);
|
|
697
|
+
return deadline
|
|
698
|
+
? this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V1', () => {
|
|
699
|
+
cancelToken.cancelled = true;
|
|
700
|
+
})
|
|
701
|
+
: transferIn;
|
|
702
|
+
});
|
|
703
|
+
const firstPacketData = yield readPacket();
|
|
336
704
|
const firstData = this.toArrayBuffer(firstPacketData.buffer.slice(1));
|
|
337
|
-
const { length, typeId, restBuffer } =
|
|
705
|
+
const { length, typeId, restBuffer } = ProtocolV1$1.decodeFirstChunk(firstData);
|
|
338
706
|
const lengthWithHeader = Number(length + HEADER_LENGTH);
|
|
339
707
|
const decoded = new ByteBuffer__default["default"](lengthWithHeader);
|
|
340
708
|
decoded.writeUint16(typeId);
|
|
@@ -343,9 +711,9 @@ class WebUsbTransport {
|
|
|
343
711
|
decoded.append(restBuffer);
|
|
344
712
|
}
|
|
345
713
|
while (decoded.offset < lengthWithHeader) {
|
|
346
|
-
const packetData = yield
|
|
714
|
+
const packetData = yield readPacket();
|
|
347
715
|
const buffer = this.toArrayBuffer(packetData.buffer.slice(1));
|
|
348
|
-
if (lengthWithHeader - decoded.offset >=
|
|
716
|
+
if (lengthWithHeader - decoded.offset >= PAYLOAD_SIZE) {
|
|
349
717
|
decoded.append(buffer);
|
|
350
718
|
}
|
|
351
719
|
else {
|
|
@@ -358,24 +726,64 @@ class WebUsbTransport {
|
|
|
358
726
|
});
|
|
359
727
|
}
|
|
360
728
|
release(path) {
|
|
729
|
+
var _a, _b;
|
|
361
730
|
return __awaiter(this, void 0, void 0, function* () {
|
|
362
731
|
const device = yield this.findDevice(path);
|
|
363
|
-
|
|
732
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
733
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
734
|
+
yield device.releaseInterface(ifaceNum);
|
|
364
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.deviceEndpoints.delete(path);
|
|
365
741
|
});
|
|
366
742
|
}
|
|
743
|
+
getProtocolType(path) {
|
|
744
|
+
return this.deviceProtocol.get(path);
|
|
745
|
+
}
|
|
367
746
|
}
|
|
368
747
|
|
|
369
|
-
const
|
|
748
|
+
const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
|
|
749
|
+
function shouldSuppressHighVolumeCallLog(name) {
|
|
750
|
+
const normalized = name.replace(/[_\s-]/g, '');
|
|
751
|
+
return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
752
|
+
}
|
|
753
|
+
function isLogBlockCommand(name) {
|
|
754
|
+
var _a, _b;
|
|
755
|
+
return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
|
|
756
|
+
}
|
|
757
|
+
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
758
|
+
function inferProtocolHintFromDeviceName(name) {
|
|
759
|
+
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
760
|
+
}
|
|
761
|
+
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
762
|
+
const BLE_PACKET_SIZE = 192;
|
|
763
|
+
const BLE_WRITE_DELAY_MS = 5;
|
|
764
|
+
const BLE_WRITE_MAX_RETRIES = 3;
|
|
765
|
+
const BLE_WRITE_RETRY_DELAY_MS = 300;
|
|
766
|
+
const BLE_RESPONSE_TIMEOUT_MS = 30000;
|
|
767
|
+
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
768
|
+
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
|
|
370
769
|
class ElectronBleTransport {
|
|
371
770
|
constructor() {
|
|
372
771
|
this.name = 'ElectronBleTransport';
|
|
373
772
|
this.configured = false;
|
|
374
773
|
this.runPromise = null;
|
|
375
774
|
this.connectedDevices = new Set();
|
|
376
|
-
this.
|
|
775
|
+
this.deviceProtocol = new Map();
|
|
776
|
+
this.deviceProtocolHints = new Map();
|
|
777
|
+
this.v1Buffers = new Map();
|
|
778
|
+
this.v2Assemblers = new Map();
|
|
779
|
+
this.v2FrameQueues = new Map();
|
|
780
|
+
this.v2FramePromises = new Map();
|
|
781
|
+
this.activeProtocolV2Call = null;
|
|
782
|
+
this.nextProtocolV2CallToken = 1;
|
|
377
783
|
this.notificationCleanups = new Map();
|
|
378
784
|
this.disconnectCleanups = new Map();
|
|
785
|
+
this.notificationTokens = new Map();
|
|
786
|
+
this.nextNotificationToken = 1;
|
|
379
787
|
}
|
|
380
788
|
handleBluetoothError(error) {
|
|
381
789
|
if (error && typeof error === 'object') {
|
|
@@ -407,8 +815,16 @@ class ElectronBleTransport {
|
|
|
407
815
|
throw error;
|
|
408
816
|
}
|
|
409
817
|
cleanupDeviceState(deviceId) {
|
|
818
|
+
var _a;
|
|
410
819
|
this.connectedDevices.delete(deviceId);
|
|
411
|
-
this.
|
|
820
|
+
this.deviceProtocol.delete(deviceId);
|
|
821
|
+
this.v1Buffers.delete(deviceId);
|
|
822
|
+
this.v2Assemblers.delete(deviceId);
|
|
823
|
+
this.resetProtocolV2Frames(deviceId);
|
|
824
|
+
if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
|
|
825
|
+
this.activeProtocolV2Call = null;
|
|
826
|
+
}
|
|
827
|
+
this.notificationTokens.delete(deviceId);
|
|
412
828
|
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
413
829
|
if (notifyCleanup) {
|
|
414
830
|
notifyCleanup();
|
|
@@ -427,39 +843,59 @@ class ElectronBleTransport {
|
|
|
427
843
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
428
844
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Noble BLE API is not available. Please ensure you are running in Electron with Noble support.');
|
|
429
845
|
}
|
|
430
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[
|
|
846
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Transport initialized');
|
|
431
847
|
}
|
|
432
848
|
configure(signedData) {
|
|
433
|
-
|
|
849
|
+
this._messages = parseConfigure(signedData);
|
|
434
850
|
this.configured = true;
|
|
435
|
-
this._messages = messages;
|
|
436
851
|
}
|
|
437
|
-
|
|
852
|
+
configureProtocolV2(signedData) {
|
|
853
|
+
var _a;
|
|
854
|
+
this._messagesV2 = parseConfigure(signedData);
|
|
855
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V2 schema configured');
|
|
856
|
+
}
|
|
857
|
+
listen() {
|
|
858
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
859
|
+
return this.enumerate();
|
|
860
|
+
});
|
|
861
|
+
}
|
|
438
862
|
enumerate() {
|
|
439
|
-
var _a, _b;
|
|
863
|
+
var _a, _b, _c, _d;
|
|
440
864
|
return __awaiter(this, void 0, void 0, function* () {
|
|
441
865
|
try {
|
|
442
866
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
443
867
|
throw new Error('Noble BLE API not available');
|
|
444
868
|
}
|
|
445
869
|
const devices = yield window.desktopApi.nobleBle.enumerate();
|
|
446
|
-
|
|
870
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
|
|
871
|
+
for (const dev of devices) {
|
|
872
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
|
|
873
|
+
const protocolHint = inferProtocolHintFromDeviceName(dev.name);
|
|
874
|
+
if (protocolHint) {
|
|
875
|
+
this.deviceProtocolHints.set(dev.id, protocolHint);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return devices.map(device => toBleDescriptor(device));
|
|
447
879
|
}
|
|
448
880
|
catch (error) {
|
|
449
|
-
(
|
|
881
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] enumerate failed:', error);
|
|
450
882
|
this.handleBluetoothError(error);
|
|
451
883
|
}
|
|
452
884
|
});
|
|
453
885
|
}
|
|
454
886
|
acquire(input) {
|
|
455
|
-
var _a, _b, _c;
|
|
887
|
+
var _a, _b, _c, _d, _e, _f;
|
|
456
888
|
return __awaiter(this, void 0, void 0, function* () {
|
|
457
|
-
const { uuid, forceCleanRunPromise } = input;
|
|
889
|
+
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
458
890
|
if (!uuid) {
|
|
459
891
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
460
892
|
}
|
|
461
893
|
if (forceCleanRunPromise && this.runPromise) {
|
|
462
|
-
|
|
894
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
895
|
+
this.runPromise.reject(error);
|
|
896
|
+
this.rejectAllProtocolV2Frames(error);
|
|
897
|
+
this.runPromise = null;
|
|
898
|
+
this.activeProtocolV2Call = null;
|
|
463
899
|
}
|
|
464
900
|
try {
|
|
465
901
|
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
@@ -469,6 +905,12 @@ class ElectronBleTransport {
|
|
|
469
905
|
if (!device) {
|
|
470
906
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
|
|
471
907
|
}
|
|
908
|
+
const protocolHint = expectedProtocol
|
|
909
|
+
? undefined
|
|
910
|
+
: (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
|
|
911
|
+
if (protocolHint) {
|
|
912
|
+
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
913
|
+
}
|
|
472
914
|
try {
|
|
473
915
|
yield window.desktopApi.nobleBle.connect(uuid);
|
|
474
916
|
this.connectedDevices.add(uuid);
|
|
@@ -476,13 +918,10 @@ class ElectronBleTransport {
|
|
|
476
918
|
catch (error) {
|
|
477
919
|
this.handleBluetoothError(error);
|
|
478
920
|
}
|
|
479
|
-
this.
|
|
921
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
922
|
+
this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
|
|
480
923
|
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
481
|
-
const cleanup =
|
|
482
|
-
if (deviceId === uuid) {
|
|
483
|
-
this.handleNotificationData(uuid, data);
|
|
484
|
-
}
|
|
485
|
-
});
|
|
924
|
+
const cleanup = this.createNotificationSubscription(uuid);
|
|
486
925
|
this.notificationCleanups.set(uuid, cleanup);
|
|
487
926
|
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
488
927
|
var _a;
|
|
@@ -496,138 +935,511 @@ class ElectronBleTransport {
|
|
|
496
935
|
}
|
|
497
936
|
});
|
|
498
937
|
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
499
|
-
|
|
938
|
+
const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
939
|
+
(_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
|
|
500
940
|
name: device.name,
|
|
501
941
|
id: device.id,
|
|
502
942
|
connectId: device.id,
|
|
503
943
|
});
|
|
504
|
-
return {
|
|
944
|
+
return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
|
|
505
945
|
}
|
|
506
946
|
catch (error) {
|
|
507
|
-
(
|
|
947
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
|
|
948
|
+
try {
|
|
949
|
+
if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
|
|
950
|
+
yield window.desktopApi.nobleBle.unsubscribe(uuid);
|
|
951
|
+
yield window.desktopApi.nobleBle.disconnect(uuid);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
catch (cleanupError) {
|
|
955
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
|
|
956
|
+
}
|
|
957
|
+
this.cleanupDeviceState(uuid);
|
|
508
958
|
throw error;
|
|
509
959
|
}
|
|
510
960
|
});
|
|
511
961
|
}
|
|
512
962
|
release(id) {
|
|
513
|
-
var _a, _b
|
|
963
|
+
var _a, _b;
|
|
514
964
|
return __awaiter(this, void 0, void 0, function* () {
|
|
515
965
|
try {
|
|
516
966
|
if (this.connectedDevices.has(id)) {
|
|
517
967
|
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
518
968
|
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
519
|
-
}
|
|
520
|
-
if ((_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) {
|
|
521
969
|
yield window.desktopApi.nobleBle.disconnect(id);
|
|
522
970
|
}
|
|
523
971
|
this.cleanupDeviceState(id);
|
|
524
972
|
}
|
|
525
973
|
}
|
|
526
974
|
catch (error) {
|
|
527
|
-
(
|
|
975
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] release failed:', error);
|
|
528
976
|
this.cleanupDeviceState(id);
|
|
529
977
|
}
|
|
530
978
|
});
|
|
531
979
|
}
|
|
532
|
-
|
|
980
|
+
createProtocolMismatchError(expected) {
|
|
981
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
982
|
+
}
|
|
983
|
+
createProtocolDetectionError() {
|
|
984
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
|
|
985
|
+
}
|
|
986
|
+
clearProbeProtocol(uuid, protocol) {
|
|
987
|
+
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
988
|
+
this.deviceProtocol.delete(uuid);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
detectProtocol(uuid, expectedProtocol, protocolHint) {
|
|
992
|
+
var _a, _b, _c;
|
|
993
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
994
|
+
if (expectedProtocol === 'V1') {
|
|
995
|
+
if (yield this.probeProtocolV1(uuid)) {
|
|
996
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
997
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected)`);
|
|
998
|
+
return 'V1';
|
|
999
|
+
}
|
|
1000
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1001
|
+
}
|
|
1002
|
+
if (expectedProtocol === 'V2') {
|
|
1003
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1004
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
|
|
1005
|
+
return 'V2';
|
|
1006
|
+
}
|
|
1007
|
+
const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1008
|
+
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1009
|
+
const protocol = probeOrder[i];
|
|
1010
|
+
if (i > 0) {
|
|
1011
|
+
yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1012
|
+
}
|
|
1013
|
+
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1014
|
+
if (detected) {
|
|
1015
|
+
this.deviceProtocol.set(uuid, protocol);
|
|
1016
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
|
|
1017
|
+
return protocol;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
this.deviceProtocol.delete(uuid);
|
|
1021
|
+
throw this.createProtocolDetectionError();
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
createNotificationSubscription(uuid) {
|
|
1025
|
+
var _a;
|
|
1026
|
+
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
1027
|
+
throw new Error('Noble BLE API not available');
|
|
1028
|
+
}
|
|
1029
|
+
const notificationToken = this.nextNotificationToken;
|
|
1030
|
+
this.nextNotificationToken += 1;
|
|
1031
|
+
this.notificationTokens.set(uuid, notificationToken);
|
|
1032
|
+
return window.desktopApi.nobleBle.onNotification((deviceId, data) => {
|
|
1033
|
+
if (deviceId === uuid && this.notificationTokens.get(uuid) === notificationToken) {
|
|
1034
|
+
this.handleNotification(uuid, data);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
1039
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1040
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1041
|
+
this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
1042
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1043
|
+
this.resetProtocolV2Frames(uuid);
|
|
1044
|
+
if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
|
|
1045
|
+
this.activeProtocolV2Call = null;
|
|
1046
|
+
}
|
|
1047
|
+
if (this.runPromise) {
|
|
1048
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1049
|
+
this.runPromise.reject(error);
|
|
1050
|
+
this.runPromise = null;
|
|
1051
|
+
}
|
|
1052
|
+
const notifyCleanup = this.notificationCleanups.get(uuid);
|
|
1053
|
+
if (notifyCleanup) {
|
|
1054
|
+
notifyCleanup();
|
|
1055
|
+
this.notificationCleanups.delete(uuid);
|
|
1056
|
+
}
|
|
1057
|
+
this.notificationTokens.delete(uuid);
|
|
1058
|
+
try {
|
|
1059
|
+
yield ((_d = (_c = window.desktopApi) === null || _c === void 0 ? void 0 : _c.nobleBle) === null || _d === void 0 ? void 0 : _d.unsubscribe(uuid));
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1063
|
+
}
|
|
1064
|
+
try {
|
|
1065
|
+
yield ((_g = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) === null || _g === void 0 ? void 0 : _g.subscribe(uuid));
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
(_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
const cleanup = this.createNotificationSubscription(uuid);
|
|
1072
|
+
this.notificationCleanups.set(uuid, cleanup);
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
probeProtocolV1(uuid) {
|
|
1076
|
+
var _a;
|
|
1077
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1078
|
+
if (!this._messages) {
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1083
|
+
yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
catch (error) {
|
|
1087
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
1088
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
probeProtocolV2(uuid) {
|
|
1094
|
+
var _a;
|
|
1095
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1096
|
+
if (!this._messages || !this._messagesV2) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1100
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1101
|
+
const detected = yield transport.probeProtocolV2({
|
|
1102
|
+
call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
|
|
1103
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
1104
|
+
logger: this.Log,
|
|
1105
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
1106
|
+
onProbeFailed: () => {
|
|
1107
|
+
var _a;
|
|
1108
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1109
|
+
this.resetProtocolV2Frames(uuid);
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
if (!detected) {
|
|
1113
|
+
this.clearProbeProtocol(uuid, 'V2');
|
|
1114
|
+
}
|
|
1115
|
+
return detected;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
writeWithChunking(uuid, hexData) {
|
|
1119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1120
|
+
const totalBytes = hexData.length / 2;
|
|
1121
|
+
if (totalBytes <= BLE_PACKET_SIZE) {
|
|
1122
|
+
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1123
|
+
yield this.writeWithRetry(uuid, hexData);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
for (let offset = 0; offset < hexData.length;) {
|
|
1127
|
+
const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
|
|
1128
|
+
const chunkHex = hexData.substring(offset, offset + chunkHexLen);
|
|
1129
|
+
offset += chunkHexLen;
|
|
1130
|
+
yield this.writeWithRetry(uuid, chunkHex);
|
|
1131
|
+
if (offset < hexData.length) {
|
|
1132
|
+
yield hdShared.wait(BLE_WRITE_DELAY_MS);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
writeWithRetry(uuid, hexData) {
|
|
1138
|
+
var _a, _b, _c;
|
|
1139
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1140
|
+
let lastError;
|
|
1141
|
+
const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
|
|
1142
|
+
if (!nobleBle) {
|
|
1143
|
+
throw new Error('Noble BLE API not available');
|
|
1144
|
+
}
|
|
1145
|
+
for (let attempt = 1; attempt <= BLE_WRITE_MAX_RETRIES; attempt++) {
|
|
1146
|
+
try {
|
|
1147
|
+
yield nobleBle.write(uuid, hexData);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
catch (error) {
|
|
1151
|
+
lastError = error;
|
|
1152
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error(`[Electron BLE] write failed (attempt ${attempt}/${BLE_WRITE_MAX_RETRIES}):`, error);
|
|
1153
|
+
if (attempt < BLE_WRITE_MAX_RETRIES) {
|
|
1154
|
+
yield hdShared.wait(BLE_WRITE_RETRY_DELAY_MS);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write failed after ${BLE_WRITE_MAX_RETRIES} attempts: ${(_c = lastError === null || lastError === void 0 ? void 0 : lastError.message) !== null && _c !== void 0 ? _c : lastError}`);
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
handleNotification(deviceId, hexData) {
|
|
533
1162
|
var _a, _b;
|
|
534
1163
|
if (hexData === 'PAIRING_REJECTED') {
|
|
535
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[
|
|
1164
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
|
|
536
1165
|
if (this.runPromise) {
|
|
537
|
-
|
|
1166
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
|
|
1167
|
+
this.runPromise.reject(error);
|
|
1168
|
+
this.rejectAllProtocolV2Frames(error);
|
|
538
1169
|
}
|
|
539
1170
|
return;
|
|
540
1171
|
}
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.
|
|
1172
|
+
const protocol = this.deviceProtocol.get(deviceId);
|
|
1173
|
+
if (!protocol) {
|
|
1174
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Ignore notification before protocol detection:', deviceId);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (protocol === 'V2') {
|
|
1178
|
+
this.handleProtocolV2Notification(deviceId, hexData);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
this.handleProtocolV1Notification(deviceId, hexData);
|
|
1182
|
+
}
|
|
1183
|
+
handleProtocolV2Notification(deviceId, hexData) {
|
|
1184
|
+
var _a, _b, _c;
|
|
1185
|
+
try {
|
|
1186
|
+
if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== deviceId) {
|
|
1187
|
+
(_b = this.v2Assemblers.get(deviceId)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
1188
|
+
this.resetProtocolV2Frames(deviceId);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
const bytes = transport.hexToBytes(hexData);
|
|
1192
|
+
if (bytes.length === 0)
|
|
1193
|
+
return;
|
|
1194
|
+
const assembler = this.v2Assemblers.get(deviceId);
|
|
1195
|
+
if (!assembler)
|
|
1196
|
+
return;
|
|
1197
|
+
for (const frameData of assembler.drain(bytes)) {
|
|
1198
|
+
this.resolveProtocolV2Frame(deviceId, frameData);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
catch (error) {
|
|
1202
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] Protocol V2 notification error:', error);
|
|
544
1203
|
if (this.runPromise) {
|
|
545
|
-
|
|
1204
|
+
const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
1205
|
+
this.runPromise.reject(notifyError);
|
|
1206
|
+
this.rejectAllProtocolV2Frames(notifyError);
|
|
546
1207
|
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
getProtocolV2FrameQueue(uuid) {
|
|
1211
|
+
let queue = this.v2FrameQueues.get(uuid);
|
|
1212
|
+
if (!queue) {
|
|
1213
|
+
queue = [];
|
|
1214
|
+
this.v2FrameQueues.set(uuid, queue);
|
|
1215
|
+
}
|
|
1216
|
+
return queue;
|
|
1217
|
+
}
|
|
1218
|
+
resolveProtocolV2Frame(uuid, frame) {
|
|
1219
|
+
const framePromise = this.v2FramePromises.get(uuid);
|
|
1220
|
+
if (framePromise) {
|
|
1221
|
+
framePromise.resolve(frame);
|
|
1222
|
+
this.v2FramePromises.delete(uuid);
|
|
547
1223
|
return;
|
|
548
1224
|
}
|
|
549
|
-
|
|
1225
|
+
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1226
|
+
}
|
|
1227
|
+
rejectAllProtocolV2Frames(error) {
|
|
1228
|
+
this.v2FrameQueues.clear();
|
|
1229
|
+
for (const framePromise of this.v2FramePromises.values()) {
|
|
1230
|
+
framePromise.reject(error);
|
|
1231
|
+
}
|
|
1232
|
+
this.v2FramePromises.clear();
|
|
1233
|
+
}
|
|
1234
|
+
resetProtocolV2Frames(uuid) {
|
|
1235
|
+
this.v2FrameQueues.delete(uuid);
|
|
1236
|
+
this.v2FramePromises.delete(uuid);
|
|
1237
|
+
}
|
|
1238
|
+
isActiveProtocolV2Call(uuid, token) {
|
|
1239
|
+
var _a;
|
|
1240
|
+
return ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === uuid && this.activeProtocolV2Call.token === token;
|
|
1241
|
+
}
|
|
1242
|
+
readProtocolV2Frame(uuid) {
|
|
1243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1244
|
+
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
1245
|
+
if (queuedFrame) {
|
|
1246
|
+
return queuedFrame;
|
|
1247
|
+
}
|
|
1248
|
+
const framePromise = hdShared.createDeferred();
|
|
1249
|
+
this.v2FramePromises.set(uuid, framePromise);
|
|
1250
|
+
try {
|
|
1251
|
+
return yield framePromise.promise;
|
|
1252
|
+
}
|
|
1253
|
+
finally {
|
|
1254
|
+
if (this.v2FramePromises.get(uuid) === framePromise) {
|
|
1255
|
+
this.v2FramePromises.delete(uuid);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
handleProtocolV1Notification(deviceId, hexData) {
|
|
1261
|
+
var _a;
|
|
1262
|
+
const result = this.processProtocolV1Notification(deviceId, hexData);
|
|
1263
|
+
if (result.error) {
|
|
1264
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
|
|
550
1265
|
if (this.runPromise) {
|
|
551
|
-
this.runPromise.
|
|
1266
|
+
this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
552
1267
|
}
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (result.isComplete && result.completePacket && this.runPromise) {
|
|
1271
|
+
this.runPromise.resolve(result.completePacket);
|
|
553
1272
|
}
|
|
554
1273
|
}
|
|
555
|
-
call(uuid, name, data) {
|
|
556
|
-
var _a, _b
|
|
1274
|
+
call(uuid, name, data, options) {
|
|
1275
|
+
var _a, _b;
|
|
557
1276
|
return __awaiter(this, void 0, void 0, function* () {
|
|
558
|
-
if (this._messages
|
|
1277
|
+
if (!this._messages) {
|
|
559
1278
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
560
1279
|
}
|
|
561
|
-
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
562
|
-
if (this.runPromise && !forceRun) {
|
|
563
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
|
|
564
|
-
}
|
|
565
1280
|
if (!this.connectedDevices.has(uuid)) {
|
|
566
1281
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound, `Device ${uuid} not connected`);
|
|
567
1282
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Transport] Noble BLE call', 'name:', name, 'data:', {
|
|
572
|
-
file_name: data === null || data === void 0 ? void 0 : data.file_name,
|
|
573
|
-
hash: data === null || data === void 0 ? void 0 : data.hash,
|
|
574
|
-
});
|
|
1283
|
+
const protocol = this.deviceProtocol.get(uuid);
|
|
1284
|
+
if (!protocol) {
|
|
1285
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
|
|
575
1286
|
}
|
|
576
|
-
|
|
577
|
-
|
|
1287
|
+
if (shouldSuppressHighVolumeCallLog(name)) ;
|
|
1288
|
+
else if (isLogBlockCommand(name)) {
|
|
1289
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
|
|
578
1290
|
}
|
|
579
1291
|
else {
|
|
580
|
-
(
|
|
1292
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
|
|
1293
|
+
}
|
|
1294
|
+
if (protocol === 'V2') {
|
|
1295
|
+
return this.callProtocolV2(uuid, name, data, options);
|
|
1296
|
+
}
|
|
1297
|
+
return this.callProtocolV1(uuid, name, data, options);
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
callProtocolV1(uuid, name, data, options) {
|
|
1301
|
+
var _a, _b;
|
|
1302
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1303
|
+
if (!this._messages) {
|
|
1304
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1305
|
+
}
|
|
1306
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1307
|
+
if (this.runPromise && !forceRun) {
|
|
1308
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
|
|
581
1309
|
}
|
|
582
|
-
const
|
|
1310
|
+
const runPromise = hdShared.createDeferred();
|
|
1311
|
+
runPromise.promise.catch(() => undefined);
|
|
1312
|
+
this.runPromise = runPromise;
|
|
1313
|
+
const messages = this._messages;
|
|
1314
|
+
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1315
|
+
let timeout;
|
|
583
1316
|
try {
|
|
584
|
-
if (!((
|
|
1317
|
+
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
585
1318
|
throw new Error('Noble BLE write API not available');
|
|
586
1319
|
}
|
|
587
1320
|
for (let i = 0; i < buffers.length; i++) {
|
|
588
1321
|
const buffer = buffers[i];
|
|
589
1322
|
if (!buffer || typeof buffer.toString !== 'function') {
|
|
590
|
-
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.error(`[Transport] Noble BLE buffer ${i + 1} is invalid:`, buffer);
|
|
591
1323
|
throw new Error(`Buffer ${i + 1} is invalid`);
|
|
592
1324
|
}
|
|
593
1325
|
const hexString = buffer.toString('hex');
|
|
594
1326
|
if (hexString.length === 0) {
|
|
595
|
-
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.error(`[Transport] Noble BLE buffer ${i + 1} generated empty hex string`);
|
|
596
1327
|
throw new Error(`Buffer ${i + 1} is empty`);
|
|
597
1328
|
}
|
|
598
1329
|
yield window.desktopApi.nobleBle.write(uuid, hexString);
|
|
599
1330
|
}
|
|
600
|
-
const response = yield
|
|
1331
|
+
const response = yield Promise.race([
|
|
1332
|
+
runPromise.promise,
|
|
1333
|
+
new Promise((_, reject) => {
|
|
1334
|
+
if (options === null || options === void 0 ? void 0 : options.timeoutMs) {
|
|
1335
|
+
timeout = setTimeout(() => {
|
|
1336
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${options.timeoutMs}ms for ${name}`);
|
|
1337
|
+
runPromise.reject(error);
|
|
1338
|
+
reject(error);
|
|
1339
|
+
}, options.timeoutMs);
|
|
1340
|
+
}
|
|
1341
|
+
}),
|
|
1342
|
+
]);
|
|
601
1343
|
if (typeof response !== 'string') {
|
|
602
1344
|
throw new Error('Returning data is not string.');
|
|
603
1345
|
}
|
|
604
|
-
const jsonData =
|
|
1346
|
+
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
605
1347
|
return check.call(jsonData);
|
|
606
1348
|
}
|
|
607
1349
|
catch (e) {
|
|
608
|
-
(
|
|
1350
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
|
|
609
1351
|
throw e;
|
|
610
1352
|
}
|
|
611
1353
|
finally {
|
|
1354
|
+
if (timeout)
|
|
1355
|
+
clearTimeout(timeout);
|
|
1356
|
+
if (this.runPromise === runPromise) {
|
|
1357
|
+
this.runPromise = null;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
callProtocolV2(uuid, name, data, options) {
|
|
1363
|
+
var _a, _b, _c, _d, _e;
|
|
1364
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1365
|
+
if (!this._messages || !this._messagesV2) {
|
|
1366
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
1367
|
+
}
|
|
1368
|
+
const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
|
|
1369
|
+
if (this.runPromise) {
|
|
1370
|
+
if (!forceRun) {
|
|
1371
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
|
|
1372
|
+
}
|
|
1373
|
+
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1374
|
+
this.runPromise.reject(error);
|
|
1375
|
+
this.rejectAllProtocolV2Frames(error);
|
|
612
1376
|
this.runPromise = null;
|
|
1377
|
+
this.activeProtocolV2Call = null;
|
|
1378
|
+
}
|
|
1379
|
+
const runPromise = hdShared.createDeferred();
|
|
1380
|
+
runPromise.promise.catch(() => undefined);
|
|
1381
|
+
this.runPromise = runPromise;
|
|
1382
|
+
const callToken = this.nextProtocolV2CallToken++;
|
|
1383
|
+
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1384
|
+
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1385
|
+
this.resetProtocolV2Frames(uuid);
|
|
1386
|
+
let completed = false;
|
|
1387
|
+
const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : BLE_RESPONSE_TIMEOUT_MS });
|
|
1388
|
+
try {
|
|
1389
|
+
const session = new transport.ProtocolV2Session({
|
|
1390
|
+
schemas: {
|
|
1391
|
+
protocolV1: this._messages,
|
|
1392
|
+
protocolV2: this._messagesV2,
|
|
1393
|
+
},
|
|
1394
|
+
router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1395
|
+
writeFrame: (frame) => this.writeWithChunking(uuid, transport.bytesToHex(frame)),
|
|
1396
|
+
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1397
|
+
const rxFrame = yield this.readProtocolV2Frame(uuid);
|
|
1398
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1399
|
+
throw new Error('Response is not Uint8Array');
|
|
1400
|
+
}
|
|
1401
|
+
return rxFrame;
|
|
1402
|
+
}),
|
|
1403
|
+
logger: this.Log,
|
|
1404
|
+
logPrefix: 'ProtocolV2 BLE',
|
|
1405
|
+
createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
|
|
1406
|
+
});
|
|
1407
|
+
const result = yield session.call(name, data, callOptions);
|
|
1408
|
+
completed = true;
|
|
1409
|
+
return result;
|
|
1410
|
+
}
|
|
1411
|
+
catch (e) {
|
|
1412
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1413
|
+
(_c = this.v2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
|
|
1414
|
+
this.resetProtocolV2Frames(uuid);
|
|
1415
|
+
}
|
|
1416
|
+
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] Protocol V2 call error:', e);
|
|
1417
|
+
throw e;
|
|
1418
|
+
}
|
|
1419
|
+
finally {
|
|
1420
|
+
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1421
|
+
if (!completed) {
|
|
1422
|
+
(_e = this.v2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
|
|
1423
|
+
}
|
|
1424
|
+
this.resetProtocolV2Frames(uuid);
|
|
1425
|
+
this.activeProtocolV2Call = null;
|
|
1426
|
+
}
|
|
1427
|
+
if (this.runPromise === runPromise) {
|
|
1428
|
+
this.runPromise = null;
|
|
1429
|
+
}
|
|
613
1430
|
}
|
|
614
1431
|
});
|
|
615
1432
|
}
|
|
616
|
-
|
|
1433
|
+
processProtocolV1Notification(deviceId, hexData) {
|
|
617
1434
|
try {
|
|
618
1435
|
if (typeof hexData !== 'string') {
|
|
619
1436
|
return { isComplete: false, error: 'Invalid hexData type' };
|
|
620
1437
|
}
|
|
621
|
-
const
|
|
622
|
-
if (
|
|
623
|
-
return { isComplete: false, error: '
|
|
1438
|
+
const data = transport.hexToBytes(hexData);
|
|
1439
|
+
if (data.length === 0) {
|
|
1440
|
+
return { isComplete: false, error: 'Empty or invalid hex data' };
|
|
624
1441
|
}
|
|
625
|
-
const
|
|
626
|
-
if (!hexMatch) {
|
|
627
|
-
return { isComplete: false, error: 'Failed to parse hex data' };
|
|
628
|
-
}
|
|
629
|
-
const data = new Uint8Array(hexMatch.map(byte => parseInt(byte, 16)));
|
|
630
|
-
const bufferState = this.dataBuffers.get(deviceId);
|
|
1442
|
+
const bufferState = this.v1Buffers.get(deviceId);
|
|
631
1443
|
if (!bufferState) {
|
|
632
1444
|
return { isComplete: false, error: 'No buffer state for device' };
|
|
633
1445
|
}
|
|
@@ -639,14 +1451,11 @@ class ElectronBleTransport {
|
|
|
639
1451
|
else {
|
|
640
1452
|
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
641
1453
|
}
|
|
642
|
-
if (bufferState.buffer.length - transport.
|
|
1454
|
+
if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {
|
|
643
1455
|
const completeBuffer = new Uint8Array(bufferState.buffer);
|
|
644
1456
|
bufferState.bufferLength = 0;
|
|
645
1457
|
bufferState.buffer = [];
|
|
646
|
-
|
|
647
|
-
.map(b => b.toString(16).padStart(2, '0'))
|
|
648
|
-
.join('');
|
|
649
|
-
return { isComplete: true, completePacket: hexString };
|
|
1458
|
+
return { isComplete: true, completePacket: transport.bytesToHex(completeBuffer) };
|
|
650
1459
|
}
|
|
651
1460
|
return { isComplete: false };
|
|
652
1461
|
}
|
|
@@ -654,6 +1463,9 @@ class ElectronBleTransport {
|
|
|
654
1463
|
return { isComplete: false, error: `Packet processing error: ${error}` };
|
|
655
1464
|
}
|
|
656
1465
|
}
|
|
1466
|
+
getProtocolType(path) {
|
|
1467
|
+
return this.deviceProtocol.get(path);
|
|
1468
|
+
}
|
|
657
1469
|
}
|
|
658
1470
|
|
|
659
1471
|
exports.ElectronBleTransport = ElectronBleTransport;
|