@onekeyfe/hd-transport-web-device 1.2.0-alpha.14 → 1.2.0-alpha.141

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/dist/index.js CHANGED
@@ -41,14 +41,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
41
41
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
42
42
  };
43
43
 
44
- const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
45
- function shouldSuppressHighVolumeCallLog(name) {
46
- return HIGH_VOLUME_CALLS.has(name);
47
- }
48
- function createTransportCallLog(name, protocol) {
49
- return { name, protocol };
50
- }
51
-
52
44
  const { parseConfigure: parseConfigure$1, check: check$1, ProtocolV1: ProtocolV1$1 } = transport__default["default"];
53
45
  const CONFIGURATION_ID = 1;
54
46
  const INTERFACE_ID = 0;
@@ -59,20 +51,40 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
59
51
  const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
60
52
  const PACKET_IO_MAX_RETRIES = 3;
61
53
  const PACKET_IO_RETRY_DELAY = 300;
62
- const PROTOCOL_PROBE_TIMEOUT = 1000;
54
+ const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
55
+ const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
56
+ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
63
57
  function inferProtocolHintFromDeviceName$1(name) {
64
58
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
65
59
  }
66
- class WebUsbTransport {
60
+ let activeWebUsbTransport;
61
+ let usbDisconnectListenerAttached = false;
62
+ function registerActiveWebUsbTransport(instance, usb) {
63
+ activeWebUsbTransport = instance;
64
+ if (usbDisconnectListenerAttached)
65
+ return;
66
+ usbDisconnectListenerAttached = true;
67
+ usb.addEventListener('disconnect', event => {
68
+ var _a;
69
+ const serial = (_a = event.device) === null || _a === void 0 ? void 0 : _a.serialNumber;
70
+ if (typeof serial !== 'string' || serial.length === 0)
71
+ return;
72
+ activeWebUsbTransport === null || activeWebUsbTransport === void 0 ? void 0 : activeWebUsbTransport.markProtocolStale(serial);
73
+ });
74
+ }
75
+ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
67
76
  constructor() {
77
+ super({
78
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
79
+ maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
80
+ logPrefix: 'ProtocolV2 WebUSB',
81
+ });
68
82
  this.deviceProtocol = new Map();
69
83
  this.deviceProtocolHints = new Map();
70
- this.protocolV2Assemblers = new Map();
71
- this.protocolV2Sessions = new Map();
72
- this.protocolV2ReadTimeouts = new Map();
84
+ this.staleProtocolPaths = new Set();
85
+ this.acquiredPaths = new Set();
86
+ this.probedDeviceObjects = new Map();
73
87
  this.deviceEndpoints = new Map();
74
- this.mockSerialPaths = new WeakMap();
75
- this.mockSerialCounter = 0;
76
88
  this.name = 'WebUsbTransport';
77
89
  this.stopped = false;
78
90
  this.configured = false;
@@ -88,6 +100,11 @@ class WebUsbTransport {
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
+ markProtocolStale(path) {
106
+ this.staleProtocolPaths.add(path);
107
+ this.probedDeviceObjects.delete(path);
91
108
  }
92
109
  configure(signedData) {
93
110
  const messages = parseConfigure$1(signedData);
@@ -95,9 +112,18 @@ class WebUsbTransport {
95
112
  this.messages = messages;
96
113
  }
97
114
  configureProtocolV2(signedData) {
115
+ var _a;
116
+ const schemaSource = typeof signedData === 'string'
117
+ ? signedData
118
+ : (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
119
+ if (schemaSource === this.protocolV2SchemaSource)
120
+ return;
121
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
98
122
  this.messagesV2 = parseConfigure$1(signedData);
99
- this.protocolV2Sessions.clear();
100
- this.protocolV2ReadTimeouts.clear();
123
+ this.protocolV2SchemaSource = schemaSource;
124
+ if (!hadProtocolV2Schema)
125
+ return;
126
+ 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
127
  }
102
128
  promptDeviceAccess() {
103
129
  return __awaiter(this, void 0, void 0, function* () {
@@ -119,20 +145,6 @@ class WebUsbTransport {
119
145
  return this.deviceList;
120
146
  });
121
147
  }
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
148
  getConnectedDevices() {
137
149
  return __awaiter(this, void 0, void 0, function* () {
138
150
  if (!this.usb)
@@ -140,10 +152,11 @@ class WebUsbTransport {
140
152
  const devices = yield this.usb.getDevices();
141
153
  const onekeyDevices = devices.filter(dev => {
142
154
  const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
143
- return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
155
+ const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
156
+ return isOneKey && hasSerialNumber && !hdShared.isKnownTrezorWebUsbDevice(dev);
144
157
  });
145
158
  this.deviceList = onekeyDevices.map(device => {
146
- const path = this.getDevicePath(device);
159
+ const path = device.serialNumber;
147
160
  const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
148
161
  if (protocolHint) {
149
162
  this.deviceProtocolHints.set(path, protocolHint);
@@ -158,25 +171,54 @@ class WebUsbTransport {
158
171
  });
159
172
  }
160
173
  acquire(input) {
161
- var _a, _b, _c;
174
+ var _a, _b, _c, _d, _e, _f;
162
175
  return __awaiter(this, void 0, void 0, function* () {
163
176
  if (!input.path)
164
177
  return;
165
178
  try {
179
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
166
180
  yield this.closeOpenDevice(input.path);
167
181
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
168
- const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
169
- const protocolHint = input.expectedProtocol
170
- ? undefined
171
- : (_c = this.deviceProtocolHints.get(input.path)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName$1(deviceName);
172
- if (protocolHint) {
173
- this.deviceProtocolHints.set(input.path, protocolHint);
182
+ if (input.forceProtocolDetection) {
183
+ this.staleProtocolPaths.delete(input.path);
184
+ this.deviceProtocol.delete(input.path);
185
+ this.deviceProtocolHints.delete(input.path);
186
+ }
187
+ if (this.staleProtocolPaths.has(input.path)) {
188
+ this.staleProtocolPaths.delete(input.path);
189
+ this.deviceProtocol.delete(input.path);
190
+ }
191
+ if (this.deviceProtocol.has(input.path)) {
192
+ const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
193
+ if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
194
+ this.deviceProtocol.delete(input.path);
195
+ }
196
+ }
197
+ const cachedProtocol = this.deviceProtocol.get(input.path);
198
+ if (cachedProtocol && input.expectedProtocol && cachedProtocol !== input.expectedProtocol) {
199
+ this.deviceProtocol.delete(input.path);
174
200
  }
175
- yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
201
+ if (!this.deviceProtocol.has(input.path)) {
202
+ const deviceName = (_c = this.deviceList.find(device => device.path === input.path)) === null || _c === void 0 ? void 0 : _c.device.productName;
203
+ const protocolHint = input.expectedProtocol
204
+ ? undefined
205
+ : (_e = (_d = input.protocolHint) !== null && _d !== void 0 ? _d : this.deviceProtocolHints.get(input.path)) !== null && _e !== void 0 ? _e : inferProtocolHintFromDeviceName$1(deviceName);
206
+ if (protocolHint) {
207
+ this.deviceProtocolHints.set(input.path, protocolHint);
208
+ }
209
+ yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
210
+ const probedDevice = (_f = this.deviceList.find(d => d.path === input.path)) === null || _f === void 0 ? void 0 : _f.device;
211
+ if (probedDevice) {
212
+ this.probedDeviceObjects.set(input.path, probedDevice);
213
+ }
214
+ }
215
+ this.acquiredPaths.add(input.path);
176
216
  return yield Promise.resolve(input.path);
177
217
  }
178
218
  catch (e) {
179
219
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
220
+ this.acquiredPaths.delete(input.path);
221
+ yield this.closeOpenDevice(input.path);
180
222
  throw e;
181
223
  }
182
224
  });
@@ -184,29 +226,53 @@ class WebUsbTransport {
184
226
  createProtocolMismatchError(expected) {
185
227
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
186
228
  }
229
+ createProtocolProbeTimeoutError(expected, attempts) {
230
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
231
+ }
187
232
  createProtocolDetectionError() {
188
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
233
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
189
234
  }
190
235
  detectProtocol(path, expectedProtocol, protocolHint) {
236
+ var _a;
191
237
  return __awaiter(this, void 0, void 0, function* () {
192
238
  if (expectedProtocol === 'V1') {
193
239
  if (yield this.probeProtocolV1(path)) {
194
240
  this.deviceProtocol.set(path, 'V1');
195
241
  return 'V1';
196
242
  }
243
+ yield this.closeConnectionAfterProbe(path);
197
244
  throw this.createProtocolMismatchError(expectedProtocol);
198
245
  }
199
246
  if (expectedProtocol === 'V2') {
200
- this.deviceProtocol.set(path, 'V2');
201
- return 'V2';
247
+ for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
248
+ if (yield this.probeProtocolV2(path)) {
249
+ this.deviceProtocol.set(path, 'V2');
250
+ return 'V2';
251
+ }
252
+ if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
253
+ yield this.resetConnectionAfterProbe(path);
254
+ (_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}`);
255
+ }
256
+ else {
257
+ yield this.closeConnectionAfterProbe(path);
258
+ }
259
+ }
260
+ this.deviceProtocol.delete(path);
261
+ throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
202
262
  }
203
263
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
204
- for (const protocol of probeOrder) {
264
+ for (const [index, protocol] of probeOrder.entries()) {
205
265
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
206
266
  if (detected) {
207
267
  this.deviceProtocol.set(path, protocol);
208
268
  return protocol;
209
269
  }
270
+ if (index < probeOrder.length - 1) {
271
+ yield this.resetConnectionAfterProbe(path);
272
+ }
273
+ else {
274
+ yield this.closeConnectionAfterProbe(path);
275
+ }
210
276
  }
211
277
  this.deviceProtocol.delete(path);
212
278
  throw this.createProtocolDetectionError();
@@ -273,7 +339,7 @@ class WebUsbTransport {
273
339
  };
274
340
  }
275
341
  connectToDevice(path, first) {
276
- var _a, _b;
342
+ var _a;
277
343
  return __awaiter(this, void 0, void 0, function* () {
278
344
  let device = yield this.findDevice(path);
279
345
  if (!device.opened) {
@@ -297,33 +363,30 @@ class WebUsbTransport {
297
363
  }
298
364
  const endpoints = this.discoverEndpoints(device);
299
365
  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
366
  yield device.claimInterface(endpoints.interfaceNumber);
303
367
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
304
368
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
305
369
  });
306
370
  }
307
371
  closeOpenDevice(path) {
308
- var _a, _b, _c, _d, _e;
372
+ var _a, _b, _c, _d;
309
373
  return __awaiter(this, void 0, void 0, function* () {
310
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
311
- const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
374
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
312
375
  if (!(current === null || current === void 0 ? void 0 : current.opened))
313
376
  return;
314
377
  const endpoints = this.deviceEndpoints.get(path);
315
- const ifaceNum = (_c = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _c !== void 0 ? _c : this.interfaceId;
378
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
316
379
  try {
317
380
  yield current.releaseInterface(ifaceNum);
318
381
  }
319
382
  catch (error) {
320
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
383
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
321
384
  }
322
385
  try {
323
386
  yield current.close();
324
387
  }
325
388
  catch (error) {
326
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[WebUsbTransport] close before reconnect failed:', error);
389
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
327
390
  }
328
391
  });
329
392
  }
@@ -338,8 +401,18 @@ class WebUsbTransport {
338
401
  }
339
402
  });
340
403
  }
404
+ assertAcquired(path) {
405
+ if (!this.acquiredPaths.has(path)) {
406
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
407
+ }
408
+ }
341
409
  post(session, name, data) {
342
410
  return __awaiter(this, void 0, void 0, function* () {
411
+ this.assertAcquired(session);
412
+ if (this.deviceProtocol.get(session) === 'V2') {
413
+ yield this.sendProtocolV2UsbFlowControl(session, name, data);
414
+ return;
415
+ }
343
416
  yield this.call(session, name, data);
344
417
  });
345
418
  }
@@ -368,6 +441,7 @@ class WebUsbTransport {
368
441
  var _a;
369
442
  return __awaiter(this, void 0, void 0, function* () {
370
443
  this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
444
+ this.staleProtocolPaths.add(path);
371
445
  yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
372
446
  try {
373
447
  const currentDevice = yield this.findDevice(path);
@@ -485,34 +559,33 @@ class WebUsbTransport {
485
559
  throw lastError;
486
560
  });
487
561
  }
488
- resetConnectionAfterProbe(path) {
489
- var _a, _b;
562
+ transferInOnce(path, length) {
563
+ var _a;
490
564
  return __awaiter(this, void 0, void 0, function* () {
491
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
492
- this.protocolV2Sessions.delete(path);
493
- this.protocolV2ReadTimeouts.delete(path);
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);
565
+ const device = yield this.findDevice(path);
566
+ if (!device.opened) {
567
+ throw new Error('USBDevice is not open for transferIn');
510
568
  }
569
+ const endpoints = this.deviceEndpoints.get(path);
570
+ const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
571
+ const result = yield device.transferIn(endpointIn, length);
572
+ return this.getTransferInData(result);
573
+ });
574
+ }
575
+ closeConnectionAfterProbe(path) {
576
+ return __awaiter(this, void 0, void 0, function* () {
577
+ yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
578
+ yield this.closeOpenDevice(path);
579
+ });
580
+ }
581
+ resetConnectionAfterProbe(path) {
582
+ return __awaiter(this, void 0, void 0, function* () {
583
+ yield this.closeConnectionAfterProbe(path);
511
584
  yield this.getConnectedDevices();
512
585
  yield this.connect(path, false);
513
586
  });
514
587
  }
515
- withProtocolReadTimeout(_path, promise, timeoutMs, protocol, onTimeout) {
588
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
516
589
  return __awaiter(this, void 0, void 0, function* () {
517
590
  let timer;
518
591
  let timedOut = false;
@@ -527,11 +600,19 @@ class WebUsbTransport {
527
600
  return yield Promise.race([
528
601
  guardedPromise,
529
602
  new Promise((_, reject) => {
530
- timer = setTimeout(() => {
603
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
531
604
  timedOut = true;
532
605
  onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
606
+ if (protocol === 'V1') {
607
+ try {
608
+ yield this.resetConnectionAfterProbe(path);
609
+ }
610
+ catch (error) {
611
+ this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
612
+ }
613
+ }
533
614
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
534
- }, timeoutMs);
615
+ }), timeoutMs);
535
616
  }),
536
617
  ]);
537
618
  }
@@ -547,7 +628,9 @@ class WebUsbTransport {
547
628
  return false;
548
629
  }
549
630
  try {
550
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
631
+ yield this.callProtocolV1(path, 'Initialize', {}, {
632
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
633
+ });
551
634
  return true;
552
635
  }
553
636
  catch (_error) {
@@ -562,10 +645,9 @@ class WebUsbTransport {
562
645
  }
563
646
  return transport.probeProtocolV2({
564
647
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
565
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
648
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
566
649
  logger: this.Log,
567
650
  logPrefix: 'ProtocolV2 WebUSB',
568
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
569
651
  });
570
652
  });
571
653
  }
@@ -574,6 +656,7 @@ class WebUsbTransport {
574
656
  if (this.messages == null) {
575
657
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
576
658
  }
659
+ this.assertAcquired(path);
577
660
  const device = yield this.findDevice(path);
578
661
  if (!device) {
579
662
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
@@ -582,9 +665,6 @@ class WebUsbTransport {
582
665
  if (!protocol) {
583
666
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
584
667
  }
585
- if (!shouldSuppressHighVolumeCallLog(name)) {
586
- this.Log.debug('transport call', createTransportCallLog(name, protocol));
587
- }
588
668
  if (protocol === 'V2') {
589
669
  return this.callProtocolV2(path, name, data, options);
590
670
  }
@@ -613,79 +693,8 @@ class WebUsbTransport {
613
693
  });
614
694
  }
615
695
  callProtocolV2(path, name, data, options) {
616
- var _a;
617
- return __awaiter(this, void 0, void 0, function* () {
618
- const protocolV1Messages = this.messages;
619
- if (!this.messagesV2) {
620
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
621
- }
622
- if (!protocolV1Messages) {
623
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
624
- }
625
- let session = this.protocolV2Sessions.get(path);
626
- if (!session) {
627
- session = new transport.ProtocolV2Session({
628
- schemas: {
629
- protocolV1: protocolV1Messages,
630
- protocolV2: this.messagesV2,
631
- },
632
- router: transport.PROTOCOL_V2_CHANNEL_USB,
633
- writeFrame: (frame) => this.transferOutOnce(path, frame),
634
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
635
- logger: this.Log,
636
- logPrefix: 'ProtocolV2 WebUSB',
637
- createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
638
- });
639
- this.protocolV2Sessions.set(path, session);
640
- }
641
- this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
642
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
643
- try {
644
- return yield session.call(name, data, options);
645
- }
646
- catch (error) {
647
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
648
- if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
649
- try {
650
- yield this.resetConnectionAfterProbe(path);
651
- }
652
- catch (resetError) {
653
- this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
654
- }
655
- }
656
- throw error;
657
- }
658
- finally {
659
- this.protocolV2ReadTimeouts.delete(path);
660
- }
661
- });
662
- }
663
- receiveProtocolV2Frame(path, timeoutMs) {
664
696
  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;
697
+ return this.callProtocolV2Usb(path, name, data, options);
689
698
  });
690
699
  }
691
700
  receiveData(path, timeoutMs) {
@@ -726,45 +735,83 @@ class WebUsbTransport {
726
735
  });
727
736
  }
728
737
  release(path) {
729
- var _a, _b;
730
738
  return __awaiter(this, void 0, void 0, function* () {
731
- const device = yield this.findDevice(path);
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);
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);
739
+ this.acquiredPaths.delete(path);
740
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
741
+ yield this.closeOpenDevice(path);
742
742
  this.deviceEndpoints.delete(path);
743
743
  });
744
744
  }
745
+ getProtocolV2UsbSchemas() {
746
+ if (!this.messages || !this.messagesV2) {
747
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
748
+ }
749
+ return {
750
+ protocolV1: this.messages,
751
+ protocolV2: this.messagesV2,
752
+ };
753
+ }
754
+ getProtocolV2UsbLogger() {
755
+ return this.Log;
756
+ }
757
+ writeProtocolV2UsbPacket(path, frame, _context) {
758
+ return __awaiter(this, void 0, void 0, function* () {
759
+ yield this.transferOutOnce(path, frame);
760
+ });
761
+ }
762
+ readProtocolV2UsbPacket(path, _context) {
763
+ return __awaiter(this, void 0, void 0, function* () {
764
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
765
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
766
+ });
767
+ }
768
+ resetProtocolV2UsbNativeLink(path, _reason) {
769
+ return __awaiter(this, void 0, void 0, function* () {
770
+ yield this.closeOpenDevice(path);
771
+ });
772
+ }
773
+ onProtocolV2UsbLinkInvalidated(path, reason) {
774
+ var _a;
775
+ this.deviceProtocol.delete(path);
776
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
777
+ }
778
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
779
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
780
+ }
745
781
  getProtocolType(path) {
746
782
  return this.deviceProtocol.get(path);
747
783
  }
748
784
  }
749
785
 
786
+ const BLE_ATT_HEADER_BYTES = 3;
787
+ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
788
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
789
+ return fallbackPacketCapacity;
790
+ }
791
+ return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
792
+ }
793
+
750
794
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
751
795
  function inferProtocolHintFromDeviceName(name) {
752
796
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
753
797
  }
754
798
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
755
- const BLE_PACKET_SIZE = 192;
799
+ const BLE_PACKET_SIZE_FALLBACK = 192;
800
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
756
801
  const BLE_WRITE_DELAY_MS = 5;
757
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
758
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
802
+ const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
759
803
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
760
804
  class ElectronBleTransport {
761
805
  constructor() {
762
806
  this.name = 'ElectronBleTransport';
763
807
  this.configured = false;
764
808
  this.runPromise = null;
809
+ this.runPromiseDeviceId = null;
765
810
  this.connectedDevices = new Set();
766
811
  this.deviceProtocol = new Map();
767
812
  this.deviceProtocolHints = new Map();
813
+ this.deviceMtus = new Map();
814
+ this.devicePacketCapacities = new Map();
768
815
  this.v1Buffers = new Map();
769
816
  this.v2Assemblers = new Map();
770
817
  this.v2FrameQueues = new Map();
@@ -786,11 +833,13 @@ class ElectronBleTransport {
786
833
  this.rejectProtocolV2Frames(uuid, new Error(reason));
787
834
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
788
835
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
789
- yield this.release(uuid);
836
+ yield this.releaseNative(uuid);
790
837
  }
791
838
  }),
792
839
  });
840
+ this.warnedMissingRelease = false;
793
841
  this.notificationCleanups = new Map();
842
+ this.mtuCleanups = new Map();
794
843
  this.disconnectCleanups = new Map();
795
844
  this.notificationTokens = new Map();
796
845
  this.nextNotificationToken = 1;
@@ -830,15 +879,27 @@ class ElectronBleTransport {
830
879
  .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
831
880
  this.connectedDevices.delete(deviceId);
832
881
  this.deviceProtocol.delete(deviceId);
882
+ this.deviceMtus.delete(deviceId);
883
+ this.devicePacketCapacities.delete(deviceId);
833
884
  this.v1Buffers.delete(deviceId);
834
885
  this.v2Assemblers.delete(deviceId);
835
886
  this.resetProtocolV2Frames(deviceId);
836
887
  this.notificationTokens.delete(deviceId);
888
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
889
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
890
+ this.runPromise = null;
891
+ this.runPromiseDeviceId = null;
892
+ }
837
893
  const notifyCleanup = this.notificationCleanups.get(deviceId);
838
894
  if (notifyCleanup) {
839
895
  notifyCleanup();
840
896
  this.notificationCleanups.delete(deviceId);
841
897
  }
898
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
899
+ if (mtuCleanup) {
900
+ mtuCleanup();
901
+ this.mtuCleanups.delete(deviceId);
902
+ }
842
903
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
843
904
  if (disconnectCleanup) {
844
905
  disconnectCleanup();
@@ -859,10 +920,18 @@ class ElectronBleTransport {
859
920
  this.configured = true;
860
921
  }
861
922
  configureProtocolV2(signedData) {
923
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
924
+ if (this.protocolV2SchemaConfiguration === configuration) {
925
+ return;
926
+ }
927
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
862
928
  this._messagesV2 = parseConfigure(signedData);
863
- this.protocolV2Links
864
- .invalidateAllLinks('Protocol V2 schema reconfigured')
865
- .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
929
+ this.protocolV2SchemaConfiguration = configuration;
930
+ if (isReconfiguration) {
931
+ this.protocolV2Links
932
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
933
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
934
+ }
866
935
  }
867
936
  listen() {
868
937
  return __awaiter(this, void 0, void 0, function* () {
@@ -894,7 +963,7 @@ class ElectronBleTransport {
894
963
  });
895
964
  }
896
965
  acquire(input) {
897
- var _a, _b, _c, _d, _e, _f;
966
+ var _a, _b, _c, _d, _e, _f, _g, _h;
898
967
  return __awaiter(this, void 0, void 0, function* () {
899
968
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
900
969
  if (!uuid) {
@@ -906,8 +975,8 @@ class ElectronBleTransport {
906
975
  if (forceCleanRunPromise && this.runPromise) {
907
976
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
908
977
  this.runPromise.reject(error);
909
- this.rejectAllProtocolV2Frames(error);
910
978
  this.runPromise = null;
979
+ this.runPromiseDeviceId = null;
911
980
  }
912
981
  try {
913
982
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -919,7 +988,7 @@ class ElectronBleTransport {
919
988
  }
920
989
  const protocolHint = expectedProtocol
921
990
  ? undefined
922
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
991
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
923
992
  if (protocolHint) {
924
993
  this.deviceProtocolHints.set(uuid, protocolHint);
925
994
  }
@@ -930,16 +999,31 @@ class ElectronBleTransport {
930
999
  catch (error) {
931
1000
  this.handleBluetoothError(error);
932
1001
  }
1002
+ const mtuCleanup = this.createMtuSubscription(uuid);
1003
+ if (mtuCleanup) {
1004
+ this.mtuCleanups.set(uuid, mtuCleanup);
1005
+ }
933
1006
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
934
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
1007
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
935
1008
  yield window.desktopApi.nobleBle.subscribe(uuid);
1009
+ yield this.refreshBlePacketCapacity(uuid);
936
1010
  const cleanup = this.createNotificationSubscription(uuid);
937
1011
  this.notificationCleanups.set(uuid, cleanup);
1012
+ const connectionToken = this.notificationTokens.get(uuid);
1013
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
1014
+ if (protocolType === 'V2') {
1015
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
1016
+ writeMode: 'withoutResponse',
1017
+ negotiatedMtu: this.deviceMtus.get(uuid),
1018
+ packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
1019
+ });
1020
+ }
938
1021
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
939
1022
  var _a;
940
- if (disconnectedDevice.id === uuid) {
1023
+ if (disconnectedDevice.id === uuid &&
1024
+ this.notificationTokens.get(uuid) === connectionToken) {
941
1025
  this.cleanupDeviceState(uuid);
942
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
1026
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
943
1027
  name: disconnectedDevice.name,
944
1028
  id: disconnectedDevice.id,
945
1029
  connectId: disconnectedDevice.id,
@@ -947,35 +1031,46 @@ class ElectronBleTransport {
947
1031
  }
948
1032
  });
949
1033
  this.disconnectCleanups.set(uuid, disconnectCleanup);
950
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
951
- (_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
952
- name: device.name,
953
- id: device.id,
954
- connectId: device.id,
955
- });
956
1034
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
957
1035
  }
958
1036
  catch (error) {
959
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
1037
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
960
1038
  try {
961
- if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
1039
+ if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
962
1040
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
963
1041
  yield window.desktopApi.nobleBle.disconnect(uuid);
964
1042
  }
965
1043
  }
966
1044
  catch (cleanupError) {
967
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
1045
+ (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
968
1046
  }
969
1047
  this.cleanupDeviceState(uuid);
970
1048
  throw error;
971
1049
  }
972
1050
  });
973
1051
  }
974
- release(id) {
975
- var _a, _b;
1052
+ release(id, _onclose, keepSession) {
1053
+ var _a;
976
1054
  return __awaiter(this, void 0, void 0, function* () {
977
1055
  try {
978
1056
  yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
1057
+ yield this.releaseLogical(id, keepSession);
1058
+ }
1059
+ catch (error) {
1060
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
1061
+ this.cleanupDeviceState(id);
1062
+ }
1063
+ });
1064
+ }
1065
+ disconnect(id) {
1066
+ return __awaiter(this, void 0, void 0, function* () {
1067
+ return this.releaseNative(id);
1068
+ });
1069
+ }
1070
+ releaseNative(id) {
1071
+ var _a, _b;
1072
+ return __awaiter(this, void 0, void 0, function* () {
1073
+ try {
979
1074
  if (this.connectedDevices.has(id)) {
980
1075
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
981
1076
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -990,11 +1085,35 @@ class ElectronBleTransport {
990
1085
  }
991
1086
  });
992
1087
  }
1088
+ releaseLogical(id, keepSession) {
1089
+ var _a, _b, _c, _d;
1090
+ return __awaiter(this, void 0, void 0, function* () {
1091
+ try {
1092
+ if (!this.connectedDevices.has(id))
1093
+ return;
1094
+ this.cleanupDeviceState(id);
1095
+ const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
1096
+ if (!release) {
1097
+ if (!this.warnedMissingRelease) {
1098
+ this.warnedMissingRelease = true;
1099
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
1100
+ 'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
1101
+ }
1102
+ return;
1103
+ }
1104
+ yield release(id, keepSession);
1105
+ }
1106
+ catch (error) {
1107
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
1108
+ this.cleanupDeviceState(id);
1109
+ }
1110
+ });
1111
+ }
993
1112
  createProtocolMismatchError(expected) {
994
1113
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
995
1114
  }
996
1115
  createProtocolDetectionError() {
997
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1116
+ 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
1117
  }
999
1118
  clearProbeProtocol(uuid, protocol) {
1000
1119
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1013,9 +1132,12 @@ class ElectronBleTransport {
1013
1132
  throw this.createProtocolMismatchError(expectedProtocol);
1014
1133
  }
1015
1134
  if (expectedProtocol === 'V2') {
1016
- this.deviceProtocol.set(uuid, 'V2');
1017
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1018
- return 'V2';
1135
+ if (yield this.probeProtocolV2(uuid)) {
1136
+ this.deviceProtocol.set(uuid, 'V2');
1137
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1138
+ return 'V2';
1139
+ }
1140
+ throw this.createProtocolMismatchError(expectedProtocol);
1019
1141
  }
1020
1142
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1021
1143
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1049,38 +1171,22 @@ class ElectronBleTransport {
1049
1171
  });
1050
1172
  }
1051
1173
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1052
- var _a, _b, _c, _d, _e, _f, _g;
1174
+ var _a;
1053
1175
  return __awaiter(this, void 0, void 0, function* () {
1054
1176
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1055
1177
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1056
1178
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1057
1179
  this.resetProtocolV2Frames(uuid);
1058
- if (this.runPromise) {
1180
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1059
1181
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1060
1182
  this.runPromise.reject(error);
1061
1183
  this.runPromise = null;
1184
+ this.runPromiseDeviceId = null;
1062
1185
  }
1063
- const notifyCleanup = this.notificationCleanups.get(uuid);
1064
- if (notifyCleanup) {
1065
- notifyCleanup();
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;
1186
+ if (!this.notificationCleanups.has(uuid)) {
1187
+ const cleanup = this.createNotificationSubscription(uuid);
1188
+ this.notificationCleanups.set(uuid, cleanup);
1081
1189
  }
1082
- const cleanup = this.createNotificationSubscription(uuid);
1083
- this.notificationCleanups.set(uuid, cleanup);
1084
1190
  });
1085
1191
  }
1086
1192
  probeProtocolV1(uuid) {
@@ -1091,12 +1197,12 @@ class ElectronBleTransport {
1091
1197
  }
1092
1198
  try {
1093
1199
  this.deviceProtocol.set(uuid, 'V1');
1094
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1200
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1095
1201
  return true;
1096
1202
  }
1097
1203
  catch (error) {
1098
1204
  this.clearProbeProtocol(uuid, 'V1');
1099
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1205
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1100
1206
  return false;
1101
1207
  }
1102
1208
  });
@@ -1126,25 +1232,6 @@ class ElectronBleTransport {
1126
1232
  return detected;
1127
1233
  });
1128
1234
  }
1129
- writeWithChunking(uuid, hexData) {
1130
- return __awaiter(this, void 0, void 0, function* () {
1131
- const totalBytes = hexData.length / 2;
1132
- if (totalBytes <= BLE_PACKET_SIZE) {
1133
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1134
- yield this.writeOnce(uuid, hexData);
1135
- return;
1136
- }
1137
- for (let offset = 0; offset < hexData.length;) {
1138
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1139
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1140
- offset += chunkHexLen;
1141
- yield this.writeOnce(uuid, chunkHex);
1142
- if (offset < hexData.length) {
1143
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1144
- }
1145
- }
1146
- });
1147
- }
1148
1235
  writeOnce(uuid, hexData) {
1149
1236
  var _a;
1150
1237
  return __awaiter(this, void 0, void 0, function* () {
@@ -1152,7 +1239,51 @@ class ElectronBleTransport {
1152
1239
  if (!nobleBle) {
1153
1240
  throw new Error('Noble BLE API not available');
1154
1241
  }
1155
- yield nobleBle.write(uuid, hexData);
1242
+ yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1243
+ });
1244
+ }
1245
+ refreshBlePacketCapacity(uuid) {
1246
+ var _a, _b;
1247
+ return __awaiter(this, void 0, void 0, function* () {
1248
+ const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1249
+ this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1250
+ });
1251
+ }
1252
+ updateBlePacketCapacity(uuid, mtu) {
1253
+ const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
1254
+ if (typeof mtu === 'number') {
1255
+ this.deviceMtus.set(uuid, mtu);
1256
+ }
1257
+ else {
1258
+ this.deviceMtus.delete(uuid);
1259
+ }
1260
+ this.devicePacketCapacities.set(uuid, packetCapacity);
1261
+ }
1262
+ createMtuSubscription(uuid) {
1263
+ var _a, _b;
1264
+ const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
1265
+ if (!onMtuChanged)
1266
+ return undefined;
1267
+ return onMtuChanged(device => {
1268
+ if (device.id === uuid) {
1269
+ this.updateBlePacketCapacity(uuid, device.mtu);
1270
+ }
1271
+ });
1272
+ }
1273
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
1274
+ var _a;
1275
+ const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
1276
+ const shouldPace = !context.highThroughput;
1277
+ return transport.writeProtocolV2BleFrame({
1278
+ frame,
1279
+ packetCapacity,
1280
+ assertActive: assertCurrentGeneration,
1281
+ signal: context.signal,
1282
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1283
+ initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
1284
+ burstSize: shouldPace ? 1 : undefined,
1285
+ burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
1286
+ writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
1156
1287
  });
1157
1288
  }
1158
1289
  handleNotification(deviceId, hexData) {
@@ -1163,7 +1294,7 @@ class ElectronBleTransport {
1163
1294
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1164
1295
  this.rejectProtocolV2Frames(deviceId, error);
1165
1296
  }
1166
- else if (this.runPromise) {
1297
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1167
1298
  this.runPromise.reject(error);
1168
1299
  }
1169
1300
  return;
@@ -1215,16 +1346,8 @@ class ElectronBleTransport {
1215
1346
  }
1216
1347
  this.getProtocolV2FrameQueue(uuid).push(frame);
1217
1348
  }
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
1349
  resetProtocolV2Frames(uuid) {
1226
- this.v2FrameQueues.delete(uuid);
1227
- this.v2FramePromises.delete(uuid);
1350
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1228
1351
  }
1229
1352
  rejectProtocolV2Frames(uuid, error) {
1230
1353
  this.v2FrameQueues.delete(uuid);
@@ -1257,17 +1380,19 @@ class ElectronBleTransport {
1257
1380
  const result = this.processProtocolV1Notification(deviceId, hexData);
1258
1381
  if (result.error) {
1259
1382
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1260
- if (this.runPromise) {
1383
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1261
1384
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1262
1385
  }
1263
1386
  return;
1264
1387
  }
1265
- if (result.isComplete && result.completePacket && this.runPromise) {
1388
+ if (result.isComplete &&
1389
+ result.completePacket &&
1390
+ this.runPromise &&
1391
+ this.runPromiseDeviceId === deviceId) {
1266
1392
  this.runPromise.resolve(result.completePacket);
1267
1393
  }
1268
1394
  }
1269
1395
  call(uuid, name, data, options) {
1270
- var _a;
1271
1396
  return __awaiter(this, void 0, void 0, function* () {
1272
1397
  if (!this._messages) {
1273
1398
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1279,15 +1404,21 @@ class ElectronBleTransport {
1279
1404
  if (!protocol) {
1280
1405
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1281
1406
  }
1282
- if (!shouldSuppressHighVolumeCallLog(name)) {
1283
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
1284
- }
1285
1407
  if (protocol === 'V2') {
1286
1408
  return this.callProtocolV2(uuid, name, data, options);
1287
1409
  }
1288
1410
  return this.callProtocolV1(uuid, name, data, options);
1289
1411
  });
1290
1412
  }
1413
+ post(uuid, name, data) {
1414
+ return __awaiter(this, void 0, void 0, function* () {
1415
+ if (this.deviceProtocol.get(uuid) === 'V2') {
1416
+ yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
1417
+ return;
1418
+ }
1419
+ yield this.callProtocolV1(uuid, name, data);
1420
+ });
1421
+ }
1291
1422
  callProtocolV1(uuid, name, data, options) {
1292
1423
  var _a, _b;
1293
1424
  return __awaiter(this, void 0, void 0, function* () {
@@ -1301,6 +1432,7 @@ class ElectronBleTransport {
1301
1432
  const runPromise = hdShared.createDeferred();
1302
1433
  runPromise.promise.catch(() => undefined);
1303
1434
  this.runPromise = runPromise;
1435
+ this.runPromiseDeviceId = uuid;
1304
1436
  const messages = this._messages;
1305
1437
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1306
1438
  let timeout;
@@ -1339,6 +1471,17 @@ class ElectronBleTransport {
1339
1471
  }
1340
1472
  catch (e) {
1341
1473
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1474
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1475
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1476
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1477
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1478
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1479
+ this.notificationCleanups.delete(uuid);
1480
+ this.notificationTokens.delete(uuid);
1481
+ if (!isProbeTimeout) {
1482
+ yield this.releaseNative(uuid);
1483
+ }
1484
+ }
1342
1485
  throw e;
1343
1486
  }
1344
1487
  finally {
@@ -1346,22 +1489,23 @@ class ElectronBleTransport {
1346
1489
  clearTimeout(timeout);
1347
1490
  if (this.runPromise === runPromise) {
1348
1491
  this.runPromise = null;
1492
+ this.runPromiseDeviceId = null;
1349
1493
  }
1350
1494
  }
1351
1495
  });
1352
1496
  }
1353
1497
  callProtocolV2(uuid, name, data, options) {
1354
- var _a, _b;
1498
+ var _a;
1355
1499
  return __awaiter(this, void 0, void 0, function* () {
1356
1500
  if (!this._messages || !this._messagesV2) {
1357
1501
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1358
1502
  }
1359
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : BLE_RESPONSE_TIMEOUT_MS });
1503
+ const callOptions = options;
1360
1504
  try {
1361
1505
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1362
1506
  }
1363
1507
  catch (e) {
1364
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1508
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1365
1509
  throw e;
1366
1510
  }
1367
1511
  });
@@ -1376,6 +1520,7 @@ class ElectronBleTransport {
1376
1520
  };
1377
1521
  return {
1378
1522
  router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1523
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1379
1524
  generation,
1380
1525
  prepareCall: () => {
1381
1526
  var _a;
@@ -1383,10 +1528,7 @@ class ElectronBleTransport {
1383
1528
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1384
1529
  this.resetProtocolV2Frames(uuid);
1385
1530
  },
1386
- writeFrame: (frame) => {
1387
- assertCurrentGeneration();
1388
- return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1389
- },
1531
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1390
1532
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1391
1533
  assertCurrentGeneration();
1392
1534
  const rxFrame = yield this.readProtocolV2Frame(uuid);
@@ -1406,6 +1548,7 @@ class ElectronBleTransport {
1406
1548
  };
1407
1549
  }
1408
1550
  processProtocolV1Notification(deviceId, hexData) {
1551
+ var _a;
1409
1552
  try {
1410
1553
  if (typeof hexData !== 'string') {
1411
1554
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1424,6 +1567,10 @@ class ElectronBleTransport {
1424
1567
  bufferState.buffer = [...data.subarray(3)];
1425
1568
  }
1426
1569
  else {
1570
+ if (bufferState.buffer.length === 0) {
1571
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
1572
+ return { isComplete: false };
1573
+ }
1427
1574
  bufferState.buffer = bufferState.buffer.concat([...data]);
1428
1575
  }
1429
1576
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {