@onekeyfe/hd-transport-web-device 1.2.0-alpha.12 → 1.2.0-alpha.121

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,22 @@ 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
+ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
67
61
  constructor() {
62
+ super({
63
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
64
+ maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
65
+ logPrefix: 'ProtocolV2 WebUSB',
66
+ });
68
67
  this.deviceProtocol = new Map();
69
68
  this.deviceProtocolHints = new Map();
70
- this.protocolV2Assemblers = new Map();
71
- this.protocolV2Sessions = new Map();
72
- this.protocolV2ReadTimeouts = new Map();
73
69
  this.deviceEndpoints = new Map();
74
- this.mockSerialPaths = new WeakMap();
75
- this.mockSerialCounter = 0;
76
70
  this.name = 'WebUsbTransport';
77
71
  this.stopped = false;
78
72
  this.configured = false;
@@ -95,9 +89,18 @@ class WebUsbTransport {
95
89
  this.messages = messages;
96
90
  }
97
91
  configureProtocolV2(signedData) {
92
+ var _a;
93
+ const schemaSource = typeof signedData === 'string'
94
+ ? signedData
95
+ : (_a = JSON.stringify(signedData)) !== null && _a !== void 0 ? _a : String(signedData);
96
+ if (schemaSource === this.protocolV2SchemaSource)
97
+ return;
98
+ const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
98
99
  this.messagesV2 = parseConfigure$1(signedData);
99
- this.protocolV2Sessions.clear();
100
- this.protocolV2ReadTimeouts.clear();
100
+ this.protocolV2SchemaSource = schemaSource;
101
+ if (!hadProtocolV2Schema)
102
+ return;
103
+ 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
104
  }
102
105
  promptDeviceAccess() {
103
106
  return __awaiter(this, void 0, void 0, function* () {
@@ -119,28 +122,18 @@ class WebUsbTransport {
119
122
  return this.deviceList;
120
123
  });
121
124
  }
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
125
  getConnectedDevices() {
137
126
  return __awaiter(this, void 0, void 0, function* () {
138
127
  if (!this.usb)
139
128
  return [];
140
129
  const devices = yield this.usb.getDevices();
141
- const onekeyDevices = devices.filter(dev => hdShared.ONEKEY_WEBUSB_FILTER.some(desc => dev.vendorId === desc.vendorId && dev.productId === desc.productId));
130
+ const onekeyDevices = devices.filter(dev => {
131
+ const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
132
+ const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
133
+ return isOneKey && hasSerialNumber && !hdShared.isKnownTrezorWebUsbDevice(dev);
134
+ });
142
135
  this.deviceList = onekeyDevices.map(device => {
143
- const path = this.getDevicePath(device);
136
+ const path = device.serialNumber;
144
137
  const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
145
138
  if (protocolHint) {
146
139
  this.deviceProtocolHints.set(path, protocolHint);
@@ -155,17 +148,18 @@ class WebUsbTransport {
155
148
  });
156
149
  }
157
150
  acquire(input) {
158
- var _a, _b, _c;
151
+ var _a, _b, _c, _d;
159
152
  return __awaiter(this, void 0, void 0, function* () {
160
153
  if (!input.path)
161
154
  return;
162
155
  try {
156
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
163
157
  yield this.closeOpenDevice(input.path);
164
158
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
165
159
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
166
160
  const protocolHint = input.expectedProtocol
167
161
  ? undefined
168
- : (_c = this.deviceProtocolHints.get(input.path)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName$1(deviceName);
162
+ : (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
169
163
  if (protocolHint) {
170
164
  this.deviceProtocolHints.set(input.path, protocolHint);
171
165
  }
@@ -174,6 +168,7 @@ class WebUsbTransport {
174
168
  }
175
169
  catch (e) {
176
170
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
171
+ yield this.closeOpenDevice(input.path);
177
172
  throw e;
178
173
  }
179
174
  });
@@ -181,29 +176,53 @@ class WebUsbTransport {
181
176
  createProtocolMismatchError(expected) {
182
177
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
183
178
  }
179
+ createProtocolProbeTimeoutError(expected, attempts) {
180
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
181
+ }
184
182
  createProtocolDetectionError() {
185
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
183
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
186
184
  }
187
185
  detectProtocol(path, expectedProtocol, protocolHint) {
186
+ var _a;
188
187
  return __awaiter(this, void 0, void 0, function* () {
189
188
  if (expectedProtocol === 'V1') {
190
189
  if (yield this.probeProtocolV1(path)) {
191
190
  this.deviceProtocol.set(path, 'V1');
192
191
  return 'V1';
193
192
  }
193
+ yield this.closeConnectionAfterProbe(path);
194
194
  throw this.createProtocolMismatchError(expectedProtocol);
195
195
  }
196
196
  if (expectedProtocol === 'V2') {
197
- this.deviceProtocol.set(path, 'V2');
198
- return 'V2';
197
+ for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
198
+ if (yield this.probeProtocolV2(path)) {
199
+ this.deviceProtocol.set(path, 'V2');
200
+ return 'V2';
201
+ }
202
+ if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
203
+ yield this.resetConnectionAfterProbe(path);
204
+ (_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}`);
205
+ }
206
+ else {
207
+ yield this.closeConnectionAfterProbe(path);
208
+ }
209
+ }
210
+ this.deviceProtocol.delete(path);
211
+ throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
199
212
  }
200
213
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
201
- for (const protocol of probeOrder) {
214
+ for (const [index, protocol] of probeOrder.entries()) {
202
215
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
203
216
  if (detected) {
204
217
  this.deviceProtocol.set(path, protocol);
205
218
  return protocol;
206
219
  }
220
+ if (index < probeOrder.length - 1) {
221
+ yield this.resetConnectionAfterProbe(path);
222
+ }
223
+ else {
224
+ yield this.closeConnectionAfterProbe(path);
225
+ }
207
226
  }
208
227
  this.deviceProtocol.delete(path);
209
228
  throw this.createProtocolDetectionError();
@@ -270,7 +289,7 @@ class WebUsbTransport {
270
289
  };
271
290
  }
272
291
  connectToDevice(path, first) {
273
- var _a, _b;
292
+ var _a;
274
293
  return __awaiter(this, void 0, void 0, function* () {
275
294
  let device = yield this.findDevice(path);
276
295
  if (!device.opened) {
@@ -294,33 +313,30 @@ class WebUsbTransport {
294
313
  }
295
314
  const endpoints = this.discoverEndpoints(device);
296
315
  this.deviceEndpoints.set(path, endpoints);
297
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
298
- this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
299
316
  yield device.claimInterface(endpoints.interfaceNumber);
300
317
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
301
318
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
302
319
  });
303
320
  }
304
321
  closeOpenDevice(path) {
305
- var _a, _b, _c, _d, _e;
322
+ var _a, _b, _c, _d;
306
323
  return __awaiter(this, void 0, void 0, function* () {
307
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
308
- const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
324
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
309
325
  if (!(current === null || current === void 0 ? void 0 : current.opened))
310
326
  return;
311
327
  const endpoints = this.deviceEndpoints.get(path);
312
- const ifaceNum = (_c = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _c !== void 0 ? _c : this.interfaceId;
328
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
313
329
  try {
314
330
  yield current.releaseInterface(ifaceNum);
315
331
  }
316
332
  catch (error) {
317
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
333
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
318
334
  }
319
335
  try {
320
336
  yield current.close();
321
337
  }
322
338
  catch (error) {
323
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[WebUsbTransport] close before reconnect failed:', error);
339
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
324
340
  }
325
341
  });
326
342
  }
@@ -482,34 +498,33 @@ class WebUsbTransport {
482
498
  throw lastError;
483
499
  });
484
500
  }
485
- resetConnectionAfterProbe(path) {
486
- var _a, _b;
501
+ transferInOnce(path, length) {
502
+ var _a;
487
503
  return __awaiter(this, void 0, void 0, function* () {
488
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
489
- this.protocolV2Sessions.delete(path);
490
- this.protocolV2ReadTimeouts.delete(path);
491
- try {
492
- const device = yield this.findDevice(path);
493
- if (device.opened) {
494
- const endpoints = this.deviceEndpoints.get(path);
495
- const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
496
- try {
497
- yield device.releaseInterface(ifaceNum);
498
- }
499
- catch (error) {
500
- this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
501
- }
502
- yield device.close();
503
- }
504
- }
505
- catch (error) {
506
- this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
504
+ const device = yield this.findDevice(path);
505
+ if (!device.opened) {
506
+ throw new Error('USBDevice is not open for transferIn');
507
507
  }
508
+ const endpoints = this.deviceEndpoints.get(path);
509
+ const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
510
+ const result = yield device.transferIn(endpointIn, length);
511
+ return this.getTransferInData(result);
512
+ });
513
+ }
514
+ closeConnectionAfterProbe(path) {
515
+ return __awaiter(this, void 0, void 0, function* () {
516
+ yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
517
+ yield this.closeOpenDevice(path);
518
+ });
519
+ }
520
+ resetConnectionAfterProbe(path) {
521
+ return __awaiter(this, void 0, void 0, function* () {
522
+ yield this.closeConnectionAfterProbe(path);
508
523
  yield this.getConnectedDevices();
509
524
  yield this.connect(path, false);
510
525
  });
511
526
  }
512
- withProtocolReadTimeout(_path, promise, timeoutMs, protocol, onTimeout) {
527
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
513
528
  return __awaiter(this, void 0, void 0, function* () {
514
529
  let timer;
515
530
  let timedOut = false;
@@ -524,11 +539,19 @@ class WebUsbTransport {
524
539
  return yield Promise.race([
525
540
  guardedPromise,
526
541
  new Promise((_, reject) => {
527
- timer = setTimeout(() => {
542
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
528
543
  timedOut = true;
529
544
  onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
545
+ if (protocol === 'V1') {
546
+ try {
547
+ yield this.resetConnectionAfterProbe(path);
548
+ }
549
+ catch (error) {
550
+ this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
551
+ }
552
+ }
530
553
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
531
- }, timeoutMs);
554
+ }), timeoutMs);
532
555
  }),
533
556
  ]);
534
557
  }
@@ -544,7 +567,9 @@ class WebUsbTransport {
544
567
  return false;
545
568
  }
546
569
  try {
547
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
570
+ yield this.callProtocolV1(path, 'Initialize', {}, {
571
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
572
+ });
548
573
  return true;
549
574
  }
550
575
  catch (_error) {
@@ -559,10 +584,9 @@ class WebUsbTransport {
559
584
  }
560
585
  return transport.probeProtocolV2({
561
586
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
562
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
587
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
563
588
  logger: this.Log,
564
589
  logPrefix: 'ProtocolV2 WebUSB',
565
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
566
590
  });
567
591
  });
568
592
  }
@@ -579,9 +603,6 @@ class WebUsbTransport {
579
603
  if (!protocol) {
580
604
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
581
605
  }
582
- if (!shouldSuppressHighVolumeCallLog(name)) {
583
- this.Log.debug('transport call', createTransportCallLog(name, protocol));
584
- }
585
606
  if (protocol === 'V2') {
586
607
  return this.callProtocolV2(path, name, data, options);
587
608
  }
@@ -610,67 +631,8 @@ class WebUsbTransport {
610
631
  });
611
632
  }
612
633
  callProtocolV2(path, name, data, options) {
613
- var _a;
614
- return __awaiter(this, void 0, void 0, function* () {
615
- const protocolV1Messages = this.messages;
616
- if (!this.messagesV2) {
617
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
618
- }
619
- if (!protocolV1Messages) {
620
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
621
- }
622
- let session = this.protocolV2Sessions.get(path);
623
- if (!session) {
624
- session = new transport.ProtocolV2Session({
625
- schemas: {
626
- protocolV1: protocolV1Messages,
627
- protocolV2: this.messagesV2,
628
- },
629
- router: transport.PROTOCOL_V2_CHANNEL_USB,
630
- writeFrame: (frame) => this.transferOutOnce(path, frame),
631
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
632
- logger: this.Log,
633
- logPrefix: 'ProtocolV2 WebUSB',
634
- createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
635
- });
636
- this.protocolV2Sessions.set(path, session);
637
- }
638
- this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
639
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
640
- try {
641
- return yield session.call(name, data, options);
642
- }
643
- finally {
644
- this.protocolV2ReadTimeouts.delete(path);
645
- }
646
- });
647
- }
648
- receiveProtocolV2Frame(path, timeoutMs) {
649
634
  return __awaiter(this, void 0, void 0, function* () {
650
- let assembler = this.protocolV2Assemblers.get(path);
651
- if (!assembler) {
652
- assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
653
- this.protocolV2Assemblers.set(path, assembler);
654
- }
655
- let frame = assembler.push(new Uint8Array(0));
656
- const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
657
- while (!frame) {
658
- const cancelToken = { cancelled: false };
659
- const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
660
- const dataView = deadline
661
- ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
662
- cancelToken.cancelled = true;
663
- })
664
- : yield transferIn;
665
- const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
666
- try {
667
- frame = assembler.push(bytes);
668
- }
669
- catch (error) {
670
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
671
- }
672
- }
673
- return frame;
635
+ return this.callProtocolV2Usb(path, name, data, options);
674
636
  });
675
637
  }
676
638
  receiveData(path, timeoutMs) {
@@ -711,43 +673,84 @@ class WebUsbTransport {
711
673
  });
712
674
  }
713
675
  release(path) {
714
- var _a, _b;
715
676
  return __awaiter(this, void 0, void 0, function* () {
716
- const device = yield this.findDevice(path);
717
- const endpoints = this.deviceEndpoints.get(path);
718
- const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
719
- yield device.releaseInterface(ifaceNum);
720
- yield device.close();
677
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
678
+ yield this.closeOpenDevice(path);
721
679
  this.deviceProtocol.delete(path);
722
680
  this.deviceProtocolHints.delete(path);
723
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
724
- this.protocolV2Assemblers.delete(path);
725
681
  this.deviceEndpoints.delete(path);
726
682
  });
727
683
  }
684
+ getProtocolV2UsbSchemas() {
685
+ if (!this.messages || !this.messagesV2) {
686
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
687
+ }
688
+ return {
689
+ protocolV1: this.messages,
690
+ protocolV2: this.messagesV2,
691
+ };
692
+ }
693
+ getProtocolV2UsbLogger() {
694
+ return this.Log;
695
+ }
696
+ writeProtocolV2UsbPacket(path, frame, _context) {
697
+ return __awaiter(this, void 0, void 0, function* () {
698
+ yield this.transferOutOnce(path, frame);
699
+ });
700
+ }
701
+ readProtocolV2UsbPacket(path, _context) {
702
+ return __awaiter(this, void 0, void 0, function* () {
703
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
704
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
705
+ });
706
+ }
707
+ resetProtocolV2UsbNativeLink(path, _reason) {
708
+ return __awaiter(this, void 0, void 0, function* () {
709
+ yield this.closeOpenDevice(path);
710
+ });
711
+ }
712
+ onProtocolV2UsbLinkInvalidated(path, reason) {
713
+ var _a;
714
+ this.deviceProtocol.delete(path);
715
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
716
+ }
717
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
718
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
719
+ }
728
720
  getProtocolType(path) {
729
721
  return this.deviceProtocol.get(path);
730
722
  }
731
723
  }
732
724
 
725
+ const BLE_ATT_HEADER_BYTES = 3;
726
+ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
727
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
728
+ return fallbackPacketCapacity;
729
+ }
730
+ return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
731
+ }
732
+
733
733
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
734
734
  function inferProtocolHintFromDeviceName(name) {
735
735
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
736
736
  }
737
737
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
738
- const BLE_PACKET_SIZE = 192;
738
+ const BLE_PACKET_SIZE_FALLBACK = 192;
739
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
739
740
  const BLE_WRITE_DELAY_MS = 5;
740
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
741
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
741
+ const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
742
742
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
743
743
  class ElectronBleTransport {
744
744
  constructor() {
745
745
  this.name = 'ElectronBleTransport';
746
746
  this.configured = false;
747
747
  this.runPromise = null;
748
+ this.runPromiseDeviceId = null;
748
749
  this.connectedDevices = new Set();
749
750
  this.deviceProtocol = new Map();
750
751
  this.deviceProtocolHints = new Map();
752
+ this.deviceMtus = new Map();
753
+ this.devicePacketCapacities = new Map();
751
754
  this.v1Buffers = new Map();
752
755
  this.v2Assemblers = new Map();
753
756
  this.v2FrameQueues = new Map();
@@ -769,11 +772,13 @@ class ElectronBleTransport {
769
772
  this.rejectProtocolV2Frames(uuid, new Error(reason));
770
773
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
771
774
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
772
- yield this.release(uuid);
775
+ yield this.releaseNative(uuid);
773
776
  }
774
777
  }),
775
778
  });
779
+ this.warnedMissingRelease = false;
776
780
  this.notificationCleanups = new Map();
781
+ this.mtuCleanups = new Map();
777
782
  this.disconnectCleanups = new Map();
778
783
  this.notificationTokens = new Map();
779
784
  this.nextNotificationToken = 1;
@@ -813,15 +818,27 @@ class ElectronBleTransport {
813
818
  .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
814
819
  this.connectedDevices.delete(deviceId);
815
820
  this.deviceProtocol.delete(deviceId);
821
+ this.deviceMtus.delete(deviceId);
822
+ this.devicePacketCapacities.delete(deviceId);
816
823
  this.v1Buffers.delete(deviceId);
817
824
  this.v2Assemblers.delete(deviceId);
818
825
  this.resetProtocolV2Frames(deviceId);
819
826
  this.notificationTokens.delete(deviceId);
827
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
828
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
829
+ this.runPromise = null;
830
+ this.runPromiseDeviceId = null;
831
+ }
820
832
  const notifyCleanup = this.notificationCleanups.get(deviceId);
821
833
  if (notifyCleanup) {
822
834
  notifyCleanup();
823
835
  this.notificationCleanups.delete(deviceId);
824
836
  }
837
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
838
+ if (mtuCleanup) {
839
+ mtuCleanup();
840
+ this.mtuCleanups.delete(deviceId);
841
+ }
825
842
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
826
843
  if (disconnectCleanup) {
827
844
  disconnectCleanup();
@@ -842,10 +859,18 @@ class ElectronBleTransport {
842
859
  this.configured = true;
843
860
  }
844
861
  configureProtocolV2(signedData) {
862
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
863
+ if (this.protocolV2SchemaConfiguration === configuration) {
864
+ return;
865
+ }
866
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
845
867
  this._messagesV2 = parseConfigure(signedData);
846
- this.protocolV2Links
847
- .invalidateAllLinks('Protocol V2 schema reconfigured')
848
- .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
868
+ this.protocolV2SchemaConfiguration = configuration;
869
+ if (isReconfiguration) {
870
+ this.protocolV2Links
871
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
872
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
873
+ }
849
874
  }
850
875
  listen() {
851
876
  return __awaiter(this, void 0, void 0, function* () {
@@ -877,7 +902,7 @@ class ElectronBleTransport {
877
902
  });
878
903
  }
879
904
  acquire(input) {
880
- var _a, _b, _c, _d, _e, _f;
905
+ var _a, _b, _c, _d, _e, _f, _g, _h;
881
906
  return __awaiter(this, void 0, void 0, function* () {
882
907
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
883
908
  if (!uuid) {
@@ -889,8 +914,8 @@ class ElectronBleTransport {
889
914
  if (forceCleanRunPromise && this.runPromise) {
890
915
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
891
916
  this.runPromise.reject(error);
892
- this.rejectAllProtocolV2Frames(error);
893
917
  this.runPromise = null;
918
+ this.runPromiseDeviceId = null;
894
919
  }
895
920
  try {
896
921
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -902,7 +927,7 @@ class ElectronBleTransport {
902
927
  }
903
928
  const protocolHint = expectedProtocol
904
929
  ? undefined
905
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
930
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
906
931
  if (protocolHint) {
907
932
  this.deviceProtocolHints.set(uuid, protocolHint);
908
933
  }
@@ -913,16 +938,31 @@ class ElectronBleTransport {
913
938
  catch (error) {
914
939
  this.handleBluetoothError(error);
915
940
  }
941
+ const mtuCleanup = this.createMtuSubscription(uuid);
942
+ if (mtuCleanup) {
943
+ this.mtuCleanups.set(uuid, mtuCleanup);
944
+ }
916
945
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
917
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
946
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
918
947
  yield window.desktopApi.nobleBle.subscribe(uuid);
948
+ yield this.refreshBlePacketCapacity(uuid);
919
949
  const cleanup = this.createNotificationSubscription(uuid);
920
950
  this.notificationCleanups.set(uuid, cleanup);
951
+ const connectionToken = this.notificationTokens.get(uuid);
952
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
953
+ if (protocolType === 'V2') {
954
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
955
+ writeMode: 'withoutResponse',
956
+ negotiatedMtu: this.deviceMtus.get(uuid),
957
+ packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
958
+ });
959
+ }
921
960
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
922
961
  var _a;
923
- if (disconnectedDevice.id === uuid) {
962
+ if (disconnectedDevice.id === uuid &&
963
+ this.notificationTokens.get(uuid) === connectionToken) {
924
964
  this.cleanupDeviceState(uuid);
925
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
965
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
926
966
  name: disconnectedDevice.name,
927
967
  id: disconnectedDevice.id,
928
968
  connectId: disconnectedDevice.id,
@@ -930,35 +970,46 @@ class ElectronBleTransport {
930
970
  }
931
971
  });
932
972
  this.disconnectCleanups.set(uuid, disconnectCleanup);
933
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
934
- (_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
935
- name: device.name,
936
- id: device.id,
937
- connectId: device.id,
938
- });
939
973
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
940
974
  }
941
975
  catch (error) {
942
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
976
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
943
977
  try {
944
- if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
978
+ if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
945
979
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
946
980
  yield window.desktopApi.nobleBle.disconnect(uuid);
947
981
  }
948
982
  }
949
983
  catch (cleanupError) {
950
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
984
+ (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
951
985
  }
952
986
  this.cleanupDeviceState(uuid);
953
987
  throw error;
954
988
  }
955
989
  });
956
990
  }
957
- release(id) {
958
- var _a, _b;
991
+ release(id, _onclose, keepSession) {
992
+ var _a;
959
993
  return __awaiter(this, void 0, void 0, function* () {
960
994
  try {
961
995
  yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
996
+ yield this.releaseLogical(id, keepSession);
997
+ }
998
+ catch (error) {
999
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
1000
+ this.cleanupDeviceState(id);
1001
+ }
1002
+ });
1003
+ }
1004
+ disconnect(id) {
1005
+ return __awaiter(this, void 0, void 0, function* () {
1006
+ return this.releaseNative(id);
1007
+ });
1008
+ }
1009
+ releaseNative(id) {
1010
+ var _a, _b;
1011
+ return __awaiter(this, void 0, void 0, function* () {
1012
+ try {
962
1013
  if (this.connectedDevices.has(id)) {
963
1014
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
964
1015
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -973,11 +1024,35 @@ class ElectronBleTransport {
973
1024
  }
974
1025
  });
975
1026
  }
1027
+ releaseLogical(id, keepSession) {
1028
+ var _a, _b, _c, _d;
1029
+ return __awaiter(this, void 0, void 0, function* () {
1030
+ try {
1031
+ if (!this.connectedDevices.has(id))
1032
+ return;
1033
+ this.cleanupDeviceState(id);
1034
+ const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
1035
+ if (!release) {
1036
+ if (!this.warnedMissingRelease) {
1037
+ this.warnedMissingRelease = true;
1038
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
1039
+ 'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
1040
+ }
1041
+ return;
1042
+ }
1043
+ yield release(id, keepSession);
1044
+ }
1045
+ catch (error) {
1046
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
1047
+ this.cleanupDeviceState(id);
1048
+ }
1049
+ });
1050
+ }
976
1051
  createProtocolMismatchError(expected) {
977
1052
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
978
1053
  }
979
1054
  createProtocolDetectionError() {
980
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1055
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
981
1056
  }
982
1057
  clearProbeProtocol(uuid, protocol) {
983
1058
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -996,9 +1071,12 @@ class ElectronBleTransport {
996
1071
  throw this.createProtocolMismatchError(expectedProtocol);
997
1072
  }
998
1073
  if (expectedProtocol === 'V2') {
999
- this.deviceProtocol.set(uuid, 'V2');
1000
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1001
- return 'V2';
1074
+ if (yield this.probeProtocolV2(uuid)) {
1075
+ this.deviceProtocol.set(uuid, 'V2');
1076
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1077
+ return 'V2';
1078
+ }
1079
+ throw this.createProtocolMismatchError(expectedProtocol);
1002
1080
  }
1003
1081
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1004
1082
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1032,16 +1110,17 @@ class ElectronBleTransport {
1032
1110
  });
1033
1111
  }
1034
1112
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1035
- var _a, _b, _c, _d, _e, _f, _g;
1113
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1036
1114
  return __awaiter(this, void 0, void 0, function* () {
1037
1115
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1038
1116
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1039
1117
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1040
1118
  this.resetProtocolV2Frames(uuid);
1041
- if (this.runPromise) {
1119
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1042
1120
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1043
1121
  this.runPromise.reject(error);
1044
1122
  this.runPromise = null;
1123
+ this.runPromiseDeviceId = null;
1045
1124
  }
1046
1125
  const notifyCleanup = this.notificationCleanups.get(uuid);
1047
1126
  if (notifyCleanup) {
@@ -1056,10 +1135,20 @@ class ElectronBleTransport {
1056
1135
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1057
1136
  }
1058
1137
  try {
1059
- yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
1138
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
1060
1139
  }
1061
1140
  catch (error) {
1062
- (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1141
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
1142
+ }
1143
+ this.connectedDevices.delete(uuid);
1144
+ try {
1145
+ yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
1146
+ this.connectedDevices.add(uuid);
1147
+ yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
1148
+ yield this.refreshBlePacketCapacity(uuid);
1149
+ }
1150
+ catch (error) {
1151
+ (_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
1063
1152
  throw error;
1064
1153
  }
1065
1154
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1074,12 +1163,12 @@ class ElectronBleTransport {
1074
1163
  }
1075
1164
  try {
1076
1165
  this.deviceProtocol.set(uuid, 'V1');
1077
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1166
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1078
1167
  return true;
1079
1168
  }
1080
1169
  catch (error) {
1081
1170
  this.clearProbeProtocol(uuid, 'V1');
1082
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1171
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1083
1172
  return false;
1084
1173
  }
1085
1174
  });
@@ -1109,25 +1198,6 @@ class ElectronBleTransport {
1109
1198
  return detected;
1110
1199
  });
1111
1200
  }
1112
- writeWithChunking(uuid, hexData) {
1113
- return __awaiter(this, void 0, void 0, function* () {
1114
- const totalBytes = hexData.length / 2;
1115
- if (totalBytes <= BLE_PACKET_SIZE) {
1116
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1117
- yield this.writeOnce(uuid, hexData);
1118
- return;
1119
- }
1120
- for (let offset = 0; offset < hexData.length;) {
1121
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1122
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1123
- offset += chunkHexLen;
1124
- yield this.writeOnce(uuid, chunkHex);
1125
- if (offset < hexData.length) {
1126
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1127
- }
1128
- }
1129
- });
1130
- }
1131
1201
  writeOnce(uuid, hexData) {
1132
1202
  var _a;
1133
1203
  return __awaiter(this, void 0, void 0, function* () {
@@ -1135,7 +1205,51 @@ class ElectronBleTransport {
1135
1205
  if (!nobleBle) {
1136
1206
  throw new Error('Noble BLE API not available');
1137
1207
  }
1138
- yield nobleBle.write(uuid, hexData);
1208
+ yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1209
+ });
1210
+ }
1211
+ refreshBlePacketCapacity(uuid) {
1212
+ var _a, _b;
1213
+ return __awaiter(this, void 0, void 0, function* () {
1214
+ const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1215
+ this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1216
+ });
1217
+ }
1218
+ updateBlePacketCapacity(uuid, mtu) {
1219
+ const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
1220
+ if (typeof mtu === 'number') {
1221
+ this.deviceMtus.set(uuid, mtu);
1222
+ }
1223
+ else {
1224
+ this.deviceMtus.delete(uuid);
1225
+ }
1226
+ this.devicePacketCapacities.set(uuid, packetCapacity);
1227
+ }
1228
+ createMtuSubscription(uuid) {
1229
+ var _a, _b;
1230
+ const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
1231
+ if (!onMtuChanged)
1232
+ return undefined;
1233
+ return onMtuChanged(device => {
1234
+ if (device.id === uuid) {
1235
+ this.updateBlePacketCapacity(uuid, device.mtu);
1236
+ }
1237
+ });
1238
+ }
1239
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
1240
+ var _a;
1241
+ const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
1242
+ const shouldPace = !context.highThroughput;
1243
+ return transport.writeProtocolV2BleFrame({
1244
+ frame,
1245
+ packetCapacity,
1246
+ assertActive: assertCurrentGeneration,
1247
+ signal: context.signal,
1248
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1249
+ initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
1250
+ burstSize: shouldPace ? 1 : undefined,
1251
+ burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
1252
+ writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
1139
1253
  });
1140
1254
  }
1141
1255
  handleNotification(deviceId, hexData) {
@@ -1146,7 +1260,7 @@ class ElectronBleTransport {
1146
1260
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1147
1261
  this.rejectProtocolV2Frames(deviceId, error);
1148
1262
  }
1149
- else if (this.runPromise) {
1263
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1150
1264
  this.runPromise.reject(error);
1151
1265
  }
1152
1266
  return;
@@ -1198,16 +1312,8 @@ class ElectronBleTransport {
1198
1312
  }
1199
1313
  this.getProtocolV2FrameQueue(uuid).push(frame);
1200
1314
  }
1201
- rejectAllProtocolV2Frames(error) {
1202
- this.v2FrameQueues.clear();
1203
- for (const framePromise of this.v2FramePromises.values()) {
1204
- framePromise.reject(error);
1205
- }
1206
- this.v2FramePromises.clear();
1207
- }
1208
1315
  resetProtocolV2Frames(uuid) {
1209
- this.v2FrameQueues.delete(uuid);
1210
- this.v2FramePromises.delete(uuid);
1316
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1211
1317
  }
1212
1318
  rejectProtocolV2Frames(uuid, error) {
1213
1319
  this.v2FrameQueues.delete(uuid);
@@ -1240,17 +1346,19 @@ class ElectronBleTransport {
1240
1346
  const result = this.processProtocolV1Notification(deviceId, hexData);
1241
1347
  if (result.error) {
1242
1348
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1243
- if (this.runPromise) {
1349
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1244
1350
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1245
1351
  }
1246
1352
  return;
1247
1353
  }
1248
- if (result.isComplete && result.completePacket && this.runPromise) {
1354
+ if (result.isComplete &&
1355
+ result.completePacket &&
1356
+ this.runPromise &&
1357
+ this.runPromiseDeviceId === deviceId) {
1249
1358
  this.runPromise.resolve(result.completePacket);
1250
1359
  }
1251
1360
  }
1252
1361
  call(uuid, name, data, options) {
1253
- var _a;
1254
1362
  return __awaiter(this, void 0, void 0, function* () {
1255
1363
  if (!this._messages) {
1256
1364
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1262,9 +1370,6 @@ class ElectronBleTransport {
1262
1370
  if (!protocol) {
1263
1371
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1264
1372
  }
1265
- if (!shouldSuppressHighVolumeCallLog(name)) {
1266
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
1267
- }
1268
1373
  if (protocol === 'V2') {
1269
1374
  return this.callProtocolV2(uuid, name, data, options);
1270
1375
  }
@@ -1284,6 +1389,7 @@ class ElectronBleTransport {
1284
1389
  const runPromise = hdShared.createDeferred();
1285
1390
  runPromise.promise.catch(() => undefined);
1286
1391
  this.runPromise = runPromise;
1392
+ this.runPromiseDeviceId = uuid;
1287
1393
  const messages = this._messages;
1288
1394
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1289
1395
  let timeout;
@@ -1322,6 +1428,17 @@ class ElectronBleTransport {
1322
1428
  }
1323
1429
  catch (e) {
1324
1430
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1431
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1432
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1433
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1434
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1435
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1436
+ this.notificationCleanups.delete(uuid);
1437
+ this.notificationTokens.delete(uuid);
1438
+ if (!isProbeTimeout) {
1439
+ yield this.releaseNative(uuid);
1440
+ }
1441
+ }
1325
1442
  throw e;
1326
1443
  }
1327
1444
  finally {
@@ -1329,22 +1446,23 @@ class ElectronBleTransport {
1329
1446
  clearTimeout(timeout);
1330
1447
  if (this.runPromise === runPromise) {
1331
1448
  this.runPromise = null;
1449
+ this.runPromiseDeviceId = null;
1332
1450
  }
1333
1451
  }
1334
1452
  });
1335
1453
  }
1336
1454
  callProtocolV2(uuid, name, data, options) {
1337
- var _a, _b;
1455
+ var _a;
1338
1456
  return __awaiter(this, void 0, void 0, function* () {
1339
1457
  if (!this._messages || !this._messagesV2) {
1340
1458
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1341
1459
  }
1342
- 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 });
1460
+ const callOptions = options;
1343
1461
  try {
1344
1462
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1345
1463
  }
1346
1464
  catch (e) {
1347
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1465
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1348
1466
  throw e;
1349
1467
  }
1350
1468
  });
@@ -1359,6 +1477,7 @@ class ElectronBleTransport {
1359
1477
  };
1360
1478
  return {
1361
1479
  router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1480
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1362
1481
  generation,
1363
1482
  prepareCall: () => {
1364
1483
  var _a;
@@ -1366,10 +1485,7 @@ class ElectronBleTransport {
1366
1485
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1367
1486
  this.resetProtocolV2Frames(uuid);
1368
1487
  },
1369
- writeFrame: (frame) => {
1370
- assertCurrentGeneration();
1371
- return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1372
- },
1488
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1373
1489
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1374
1490
  assertCurrentGeneration();
1375
1491
  const rxFrame = yield this.readProtocolV2Frame(uuid);
@@ -1389,6 +1505,7 @@ class ElectronBleTransport {
1389
1505
  };
1390
1506
  }
1391
1507
  processProtocolV1Notification(deviceId, hexData) {
1508
+ var _a;
1392
1509
  try {
1393
1510
  if (typeof hexData !== 'string') {
1394
1511
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1407,6 +1524,10 @@ class ElectronBleTransport {
1407
1524
  bufferState.buffer = [...data.subarray(3)];
1408
1525
  }
1409
1526
  else {
1527
+ if (bufferState.buffer.length === 0) {
1528
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
1529
+ return { isComplete: false };
1530
+ }
1410
1531
  bufferState.buffer = bufferState.buffer.concat([...data]);
1411
1532
  }
1412
1533
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {