@onekeyfe/hd-transport-web-device 1.2.0-alpha.13 → 1.2.0-alpha.131

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,20 +122,6 @@ 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)
@@ -140,10 +129,11 @@ class WebUsbTransport {
140
129
  const devices = yield this.usb.getDevices();
141
130
  const onekeyDevices = devices.filter(dev => {
142
131
  const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
143
- return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
132
+ const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
133
+ return isOneKey && hasSerialNumber && !hdShared.isKnownTrezorWebUsbDevice(dev);
144
134
  });
145
135
  this.deviceList = onekeyDevices.map(device => {
146
- const path = this.getDevicePath(device);
136
+ const path = device.serialNumber;
147
137
  const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
148
138
  if (protocolHint) {
149
139
  this.deviceProtocolHints.set(path, protocolHint);
@@ -158,17 +148,18 @@ class WebUsbTransport {
158
148
  });
159
149
  }
160
150
  acquire(input) {
161
- var _a, _b, _c;
151
+ var _a, _b, _c, _d;
162
152
  return __awaiter(this, void 0, void 0, function* () {
163
153
  if (!input.path)
164
154
  return;
165
155
  try {
156
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
166
157
  yield this.closeOpenDevice(input.path);
167
158
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
168
159
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
169
160
  const protocolHint = input.expectedProtocol
170
161
  ? undefined
171
- : (_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);
172
163
  if (protocolHint) {
173
164
  this.deviceProtocolHints.set(input.path, protocolHint);
174
165
  }
@@ -177,6 +168,7 @@ class WebUsbTransport {
177
168
  }
178
169
  catch (e) {
179
170
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
171
+ yield this.closeOpenDevice(input.path);
180
172
  throw e;
181
173
  }
182
174
  });
@@ -184,29 +176,53 @@ class WebUsbTransport {
184
176
  createProtocolMismatchError(expected) {
185
177
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
186
178
  }
179
+ createProtocolProbeTimeoutError(expected, attempts) {
180
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
181
+ }
187
182
  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');
183
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
189
184
  }
190
185
  detectProtocol(path, expectedProtocol, protocolHint) {
186
+ var _a;
191
187
  return __awaiter(this, void 0, void 0, function* () {
192
188
  if (expectedProtocol === 'V1') {
193
189
  if (yield this.probeProtocolV1(path)) {
194
190
  this.deviceProtocol.set(path, 'V1');
195
191
  return 'V1';
196
192
  }
193
+ yield this.closeConnectionAfterProbe(path);
197
194
  throw this.createProtocolMismatchError(expectedProtocol);
198
195
  }
199
196
  if (expectedProtocol === 'V2') {
200
- this.deviceProtocol.set(path, 'V2');
201
- 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);
202
212
  }
203
213
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
204
- for (const protocol of probeOrder) {
214
+ for (const [index, protocol] of probeOrder.entries()) {
205
215
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
206
216
  if (detected) {
207
217
  this.deviceProtocol.set(path, protocol);
208
218
  return protocol;
209
219
  }
220
+ if (index < probeOrder.length - 1) {
221
+ yield this.resetConnectionAfterProbe(path);
222
+ }
223
+ else {
224
+ yield this.closeConnectionAfterProbe(path);
225
+ }
210
226
  }
211
227
  this.deviceProtocol.delete(path);
212
228
  throw this.createProtocolDetectionError();
@@ -273,7 +289,7 @@ class WebUsbTransport {
273
289
  };
274
290
  }
275
291
  connectToDevice(path, first) {
276
- var _a, _b;
292
+ var _a;
277
293
  return __awaiter(this, void 0, void 0, function* () {
278
294
  let device = yield this.findDevice(path);
279
295
  if (!device.opened) {
@@ -297,33 +313,30 @@ class WebUsbTransport {
297
313
  }
298
314
  const endpoints = this.discoverEndpoints(device);
299
315
  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
316
  yield device.claimInterface(endpoints.interfaceNumber);
303
317
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
304
318
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
305
319
  });
306
320
  }
307
321
  closeOpenDevice(path) {
308
- var _a, _b, _c, _d, _e;
322
+ var _a, _b, _c, _d;
309
323
  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;
324
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
312
325
  if (!(current === null || current === void 0 ? void 0 : current.opened))
313
326
  return;
314
327
  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;
328
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
316
329
  try {
317
330
  yield current.releaseInterface(ifaceNum);
318
331
  }
319
332
  catch (error) {
320
- (_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);
321
334
  }
322
335
  try {
323
336
  yield current.close();
324
337
  }
325
338
  catch (error) {
326
- (_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);
327
340
  }
328
341
  });
329
342
  }
@@ -340,6 +353,10 @@ class WebUsbTransport {
340
353
  }
341
354
  post(session, name, data) {
342
355
  return __awaiter(this, void 0, void 0, function* () {
356
+ if (this.deviceProtocol.get(session) === 'V2') {
357
+ yield this.sendProtocolV2UsbFlowControl(session, name, data);
358
+ return;
359
+ }
343
360
  yield this.call(session, name, data);
344
361
  });
345
362
  }
@@ -485,34 +502,33 @@ class WebUsbTransport {
485
502
  throw lastError;
486
503
  });
487
504
  }
488
- resetConnectionAfterProbe(path) {
489
- var _a, _b;
505
+ transferInOnce(path, length) {
506
+ var _a;
490
507
  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);
508
+ const device = yield this.findDevice(path);
509
+ if (!device.opened) {
510
+ throw new Error('USBDevice is not open for transferIn');
510
511
  }
512
+ const endpoints = this.deviceEndpoints.get(path);
513
+ const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
514
+ const result = yield device.transferIn(endpointIn, length);
515
+ return this.getTransferInData(result);
516
+ });
517
+ }
518
+ closeConnectionAfterProbe(path) {
519
+ return __awaiter(this, void 0, void 0, function* () {
520
+ yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
521
+ yield this.closeOpenDevice(path);
522
+ });
523
+ }
524
+ resetConnectionAfterProbe(path) {
525
+ return __awaiter(this, void 0, void 0, function* () {
526
+ yield this.closeConnectionAfterProbe(path);
511
527
  yield this.getConnectedDevices();
512
528
  yield this.connect(path, false);
513
529
  });
514
530
  }
515
- withProtocolReadTimeout(_path, promise, timeoutMs, protocol, onTimeout) {
531
+ withProtocolReadTimeout(path, promise, timeoutMs, protocol, onTimeout) {
516
532
  return __awaiter(this, void 0, void 0, function* () {
517
533
  let timer;
518
534
  let timedOut = false;
@@ -527,11 +543,19 @@ class WebUsbTransport {
527
543
  return yield Promise.race([
528
544
  guardedPromise,
529
545
  new Promise((_, reject) => {
530
- timer = setTimeout(() => {
546
+ timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
531
547
  timedOut = true;
532
548
  onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
549
+ if (protocol === 'V1') {
550
+ try {
551
+ yield this.resetConnectionAfterProbe(path);
552
+ }
553
+ catch (error) {
554
+ this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
555
+ }
556
+ }
533
557
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
534
- }, timeoutMs);
558
+ }), timeoutMs);
535
559
  }),
536
560
  ]);
537
561
  }
@@ -547,7 +571,9 @@ class WebUsbTransport {
547
571
  return false;
548
572
  }
549
573
  try {
550
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
574
+ yield this.callProtocolV1(path, 'Initialize', {}, {
575
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
576
+ });
551
577
  return true;
552
578
  }
553
579
  catch (_error) {
@@ -562,10 +588,9 @@ class WebUsbTransport {
562
588
  }
563
589
  return transport.probeProtocolV2({
564
590
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
565
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
591
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
566
592
  logger: this.Log,
567
593
  logPrefix: 'ProtocolV2 WebUSB',
568
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
569
594
  });
570
595
  });
571
596
  }
@@ -582,9 +607,6 @@ class WebUsbTransport {
582
607
  if (!protocol) {
583
608
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
584
609
  }
585
- if (!shouldSuppressHighVolumeCallLog(name)) {
586
- this.Log.debug('transport call', createTransportCallLog(name, protocol));
587
- }
588
610
  if (protocol === 'V2') {
589
611
  return this.callProtocolV2(path, name, data, options);
590
612
  }
@@ -613,79 +635,8 @@ class WebUsbTransport {
613
635
  });
614
636
  }
615
637
  callProtocolV2(path, name, data, options) {
616
- var _a;
617
638
  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
- 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;
639
+ return this.callProtocolV2Usb(path, name, data, options);
689
640
  });
690
641
  }
691
642
  receiveData(path, timeoutMs) {
@@ -726,45 +677,84 @@ class WebUsbTransport {
726
677
  });
727
678
  }
728
679
  release(path) {
729
- var _a, _b;
730
680
  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();
681
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
682
+ yield this.closeOpenDevice(path);
736
683
  this.deviceProtocol.delete(path);
737
684
  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);
742
685
  this.deviceEndpoints.delete(path);
743
686
  });
744
687
  }
688
+ getProtocolV2UsbSchemas() {
689
+ if (!this.messages || !this.messagesV2) {
690
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
691
+ }
692
+ return {
693
+ protocolV1: this.messages,
694
+ protocolV2: this.messagesV2,
695
+ };
696
+ }
697
+ getProtocolV2UsbLogger() {
698
+ return this.Log;
699
+ }
700
+ writeProtocolV2UsbPacket(path, frame, _context) {
701
+ return __awaiter(this, void 0, void 0, function* () {
702
+ yield this.transferOutOnce(path, frame);
703
+ });
704
+ }
705
+ readProtocolV2UsbPacket(path, _context) {
706
+ return __awaiter(this, void 0, void 0, function* () {
707
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
708
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
709
+ });
710
+ }
711
+ resetProtocolV2UsbNativeLink(path, _reason) {
712
+ return __awaiter(this, void 0, void 0, function* () {
713
+ yield this.closeOpenDevice(path);
714
+ });
715
+ }
716
+ onProtocolV2UsbLinkInvalidated(path, reason) {
717
+ var _a;
718
+ this.deviceProtocol.delete(path);
719
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
720
+ }
721
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
722
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
723
+ }
745
724
  getProtocolType(path) {
746
725
  return this.deviceProtocol.get(path);
747
726
  }
748
727
  }
749
728
 
729
+ const BLE_ATT_HEADER_BYTES = 3;
730
+ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
731
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
732
+ return fallbackPacketCapacity;
733
+ }
734
+ return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
735
+ }
736
+
750
737
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
751
738
  function inferProtocolHintFromDeviceName(name) {
752
739
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
753
740
  }
754
741
  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;
742
+ const BLE_PACKET_SIZE_FALLBACK = 192;
743
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
756
744
  const BLE_WRITE_DELAY_MS = 5;
757
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
758
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
745
+ const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
759
746
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
760
747
  class ElectronBleTransport {
761
748
  constructor() {
762
749
  this.name = 'ElectronBleTransport';
763
750
  this.configured = false;
764
751
  this.runPromise = null;
752
+ this.runPromiseDeviceId = null;
765
753
  this.connectedDevices = new Set();
766
754
  this.deviceProtocol = new Map();
767
755
  this.deviceProtocolHints = new Map();
756
+ this.deviceMtus = new Map();
757
+ this.devicePacketCapacities = new Map();
768
758
  this.v1Buffers = new Map();
769
759
  this.v2Assemblers = new Map();
770
760
  this.v2FrameQueues = new Map();
@@ -786,11 +776,13 @@ class ElectronBleTransport {
786
776
  this.rejectProtocolV2Frames(uuid, new Error(reason));
787
777
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
788
778
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
789
- yield this.release(uuid);
779
+ yield this.releaseNative(uuid);
790
780
  }
791
781
  }),
792
782
  });
783
+ this.warnedMissingRelease = false;
793
784
  this.notificationCleanups = new Map();
785
+ this.mtuCleanups = new Map();
794
786
  this.disconnectCleanups = new Map();
795
787
  this.notificationTokens = new Map();
796
788
  this.nextNotificationToken = 1;
@@ -830,15 +822,27 @@ class ElectronBleTransport {
830
822
  .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
831
823
  this.connectedDevices.delete(deviceId);
832
824
  this.deviceProtocol.delete(deviceId);
825
+ this.deviceMtus.delete(deviceId);
826
+ this.devicePacketCapacities.delete(deviceId);
833
827
  this.v1Buffers.delete(deviceId);
834
828
  this.v2Assemblers.delete(deviceId);
835
829
  this.resetProtocolV2Frames(deviceId);
836
830
  this.notificationTokens.delete(deviceId);
831
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
832
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
833
+ this.runPromise = null;
834
+ this.runPromiseDeviceId = null;
835
+ }
837
836
  const notifyCleanup = this.notificationCleanups.get(deviceId);
838
837
  if (notifyCleanup) {
839
838
  notifyCleanup();
840
839
  this.notificationCleanups.delete(deviceId);
841
840
  }
841
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
842
+ if (mtuCleanup) {
843
+ mtuCleanup();
844
+ this.mtuCleanups.delete(deviceId);
845
+ }
842
846
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
843
847
  if (disconnectCleanup) {
844
848
  disconnectCleanup();
@@ -859,10 +863,18 @@ class ElectronBleTransport {
859
863
  this.configured = true;
860
864
  }
861
865
  configureProtocolV2(signedData) {
866
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
867
+ if (this.protocolV2SchemaConfiguration === configuration) {
868
+ return;
869
+ }
870
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
862
871
  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); });
872
+ this.protocolV2SchemaConfiguration = configuration;
873
+ if (isReconfiguration) {
874
+ this.protocolV2Links
875
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
876
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
877
+ }
866
878
  }
867
879
  listen() {
868
880
  return __awaiter(this, void 0, void 0, function* () {
@@ -894,7 +906,7 @@ class ElectronBleTransport {
894
906
  });
895
907
  }
896
908
  acquire(input) {
897
- var _a, _b, _c, _d, _e, _f;
909
+ var _a, _b, _c, _d, _e, _f, _g, _h;
898
910
  return __awaiter(this, void 0, void 0, function* () {
899
911
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
900
912
  if (!uuid) {
@@ -906,8 +918,8 @@ class ElectronBleTransport {
906
918
  if (forceCleanRunPromise && this.runPromise) {
907
919
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
908
920
  this.runPromise.reject(error);
909
- this.rejectAllProtocolV2Frames(error);
910
921
  this.runPromise = null;
922
+ this.runPromiseDeviceId = null;
911
923
  }
912
924
  try {
913
925
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -919,7 +931,7 @@ class ElectronBleTransport {
919
931
  }
920
932
  const protocolHint = expectedProtocol
921
933
  ? undefined
922
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
934
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
923
935
  if (protocolHint) {
924
936
  this.deviceProtocolHints.set(uuid, protocolHint);
925
937
  }
@@ -930,16 +942,31 @@ class ElectronBleTransport {
930
942
  catch (error) {
931
943
  this.handleBluetoothError(error);
932
944
  }
945
+ const mtuCleanup = this.createMtuSubscription(uuid);
946
+ if (mtuCleanup) {
947
+ this.mtuCleanups.set(uuid, mtuCleanup);
948
+ }
933
949
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
934
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
950
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
935
951
  yield window.desktopApi.nobleBle.subscribe(uuid);
952
+ yield this.refreshBlePacketCapacity(uuid);
936
953
  const cleanup = this.createNotificationSubscription(uuid);
937
954
  this.notificationCleanups.set(uuid, cleanup);
955
+ const connectionToken = this.notificationTokens.get(uuid);
956
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
957
+ if (protocolType === 'V2') {
958
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
959
+ writeMode: 'withoutResponse',
960
+ negotiatedMtu: this.deviceMtus.get(uuid),
961
+ packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
962
+ });
963
+ }
938
964
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
939
965
  var _a;
940
- if (disconnectedDevice.id === uuid) {
966
+ if (disconnectedDevice.id === uuid &&
967
+ this.notificationTokens.get(uuid) === connectionToken) {
941
968
  this.cleanupDeviceState(uuid);
942
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
969
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
943
970
  name: disconnectedDevice.name,
944
971
  id: disconnectedDevice.id,
945
972
  connectId: disconnectedDevice.id,
@@ -947,35 +974,46 @@ class ElectronBleTransport {
947
974
  }
948
975
  });
949
976
  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
977
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
957
978
  }
958
979
  catch (error) {
959
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
980
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
960
981
  try {
961
- if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
982
+ if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
962
983
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
963
984
  yield window.desktopApi.nobleBle.disconnect(uuid);
964
985
  }
965
986
  }
966
987
  catch (cleanupError) {
967
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
988
+ (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
968
989
  }
969
990
  this.cleanupDeviceState(uuid);
970
991
  throw error;
971
992
  }
972
993
  });
973
994
  }
974
- release(id) {
975
- var _a, _b;
995
+ release(id, _onclose, keepSession) {
996
+ var _a;
976
997
  return __awaiter(this, void 0, void 0, function* () {
977
998
  try {
978
999
  yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
1000
+ yield this.releaseLogical(id, keepSession);
1001
+ }
1002
+ catch (error) {
1003
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
1004
+ this.cleanupDeviceState(id);
1005
+ }
1006
+ });
1007
+ }
1008
+ disconnect(id) {
1009
+ return __awaiter(this, void 0, void 0, function* () {
1010
+ return this.releaseNative(id);
1011
+ });
1012
+ }
1013
+ releaseNative(id) {
1014
+ var _a, _b;
1015
+ return __awaiter(this, void 0, void 0, function* () {
1016
+ try {
979
1017
  if (this.connectedDevices.has(id)) {
980
1018
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
981
1019
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -990,11 +1028,35 @@ class ElectronBleTransport {
990
1028
  }
991
1029
  });
992
1030
  }
1031
+ releaseLogical(id, keepSession) {
1032
+ var _a, _b, _c, _d;
1033
+ return __awaiter(this, void 0, void 0, function* () {
1034
+ try {
1035
+ if (!this.connectedDevices.has(id))
1036
+ return;
1037
+ this.cleanupDeviceState(id);
1038
+ const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
1039
+ if (!release) {
1040
+ if (!this.warnedMissingRelease) {
1041
+ this.warnedMissingRelease = true;
1042
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
1043
+ 'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
1044
+ }
1045
+ return;
1046
+ }
1047
+ yield release(id, keepSession);
1048
+ }
1049
+ catch (error) {
1050
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
1051
+ this.cleanupDeviceState(id);
1052
+ }
1053
+ });
1054
+ }
993
1055
  createProtocolMismatchError(expected) {
994
1056
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
995
1057
  }
996
1058
  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');
1059
+ 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
1060
  }
999
1061
  clearProbeProtocol(uuid, protocol) {
1000
1062
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1013,9 +1075,12 @@ class ElectronBleTransport {
1013
1075
  throw this.createProtocolMismatchError(expectedProtocol);
1014
1076
  }
1015
1077
  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';
1078
+ if (yield this.probeProtocolV2(uuid)) {
1079
+ this.deviceProtocol.set(uuid, 'V2');
1080
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1081
+ return 'V2';
1082
+ }
1083
+ throw this.createProtocolMismatchError(expectedProtocol);
1019
1084
  }
1020
1085
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1021
1086
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1049,38 +1114,22 @@ class ElectronBleTransport {
1049
1114
  });
1050
1115
  }
1051
1116
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1052
- var _a, _b, _c, _d, _e, _f, _g;
1117
+ var _a;
1053
1118
  return __awaiter(this, void 0, void 0, function* () {
1054
1119
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1055
1120
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1056
1121
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1057
1122
  this.resetProtocolV2Frames(uuid);
1058
- if (this.runPromise) {
1123
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1059
1124
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1060
1125
  this.runPromise.reject(error);
1061
1126
  this.runPromise = null;
1127
+ this.runPromiseDeviceId = null;
1062
1128
  }
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;
1129
+ if (!this.notificationCleanups.has(uuid)) {
1130
+ const cleanup = this.createNotificationSubscription(uuid);
1131
+ this.notificationCleanups.set(uuid, cleanup);
1081
1132
  }
1082
- const cleanup = this.createNotificationSubscription(uuid);
1083
- this.notificationCleanups.set(uuid, cleanup);
1084
1133
  });
1085
1134
  }
1086
1135
  probeProtocolV1(uuid) {
@@ -1091,12 +1140,12 @@ class ElectronBleTransport {
1091
1140
  }
1092
1141
  try {
1093
1142
  this.deviceProtocol.set(uuid, 'V1');
1094
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1143
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1095
1144
  return true;
1096
1145
  }
1097
1146
  catch (error) {
1098
1147
  this.clearProbeProtocol(uuid, 'V1');
1099
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1148
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1100
1149
  return false;
1101
1150
  }
1102
1151
  });
@@ -1126,25 +1175,6 @@ class ElectronBleTransport {
1126
1175
  return detected;
1127
1176
  });
1128
1177
  }
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
1178
  writeOnce(uuid, hexData) {
1149
1179
  var _a;
1150
1180
  return __awaiter(this, void 0, void 0, function* () {
@@ -1152,7 +1182,51 @@ class ElectronBleTransport {
1152
1182
  if (!nobleBle) {
1153
1183
  throw new Error('Noble BLE API not available');
1154
1184
  }
1155
- yield nobleBle.write(uuid, hexData);
1185
+ yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1186
+ });
1187
+ }
1188
+ refreshBlePacketCapacity(uuid) {
1189
+ var _a, _b;
1190
+ return __awaiter(this, void 0, void 0, function* () {
1191
+ const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1192
+ this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1193
+ });
1194
+ }
1195
+ updateBlePacketCapacity(uuid, mtu) {
1196
+ const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
1197
+ if (typeof mtu === 'number') {
1198
+ this.deviceMtus.set(uuid, mtu);
1199
+ }
1200
+ else {
1201
+ this.deviceMtus.delete(uuid);
1202
+ }
1203
+ this.devicePacketCapacities.set(uuid, packetCapacity);
1204
+ }
1205
+ createMtuSubscription(uuid) {
1206
+ var _a, _b;
1207
+ const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
1208
+ if (!onMtuChanged)
1209
+ return undefined;
1210
+ return onMtuChanged(device => {
1211
+ if (device.id === uuid) {
1212
+ this.updateBlePacketCapacity(uuid, device.mtu);
1213
+ }
1214
+ });
1215
+ }
1216
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
1217
+ var _a;
1218
+ const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
1219
+ const shouldPace = !context.highThroughput;
1220
+ return transport.writeProtocolV2BleFrame({
1221
+ frame,
1222
+ packetCapacity,
1223
+ assertActive: assertCurrentGeneration,
1224
+ signal: context.signal,
1225
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1226
+ initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
1227
+ burstSize: shouldPace ? 1 : undefined,
1228
+ burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
1229
+ writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
1156
1230
  });
1157
1231
  }
1158
1232
  handleNotification(deviceId, hexData) {
@@ -1163,7 +1237,7 @@ class ElectronBleTransport {
1163
1237
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1164
1238
  this.rejectProtocolV2Frames(deviceId, error);
1165
1239
  }
1166
- else if (this.runPromise) {
1240
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1167
1241
  this.runPromise.reject(error);
1168
1242
  }
1169
1243
  return;
@@ -1215,16 +1289,8 @@ class ElectronBleTransport {
1215
1289
  }
1216
1290
  this.getProtocolV2FrameQueue(uuid).push(frame);
1217
1291
  }
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
1292
  resetProtocolV2Frames(uuid) {
1226
- this.v2FrameQueues.delete(uuid);
1227
- this.v2FramePromises.delete(uuid);
1293
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1228
1294
  }
1229
1295
  rejectProtocolV2Frames(uuid, error) {
1230
1296
  this.v2FrameQueues.delete(uuid);
@@ -1257,17 +1323,19 @@ class ElectronBleTransport {
1257
1323
  const result = this.processProtocolV1Notification(deviceId, hexData);
1258
1324
  if (result.error) {
1259
1325
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1260
- if (this.runPromise) {
1326
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1261
1327
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1262
1328
  }
1263
1329
  return;
1264
1330
  }
1265
- if (result.isComplete && result.completePacket && this.runPromise) {
1331
+ if (result.isComplete &&
1332
+ result.completePacket &&
1333
+ this.runPromise &&
1334
+ this.runPromiseDeviceId === deviceId) {
1266
1335
  this.runPromise.resolve(result.completePacket);
1267
1336
  }
1268
1337
  }
1269
1338
  call(uuid, name, data, options) {
1270
- var _a;
1271
1339
  return __awaiter(this, void 0, void 0, function* () {
1272
1340
  if (!this._messages) {
1273
1341
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1279,15 +1347,21 @@ class ElectronBleTransport {
1279
1347
  if (!protocol) {
1280
1348
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1281
1349
  }
1282
- if (!shouldSuppressHighVolumeCallLog(name)) {
1283
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', createTransportCallLog(name, protocol));
1284
- }
1285
1350
  if (protocol === 'V2') {
1286
1351
  return this.callProtocolV2(uuid, name, data, options);
1287
1352
  }
1288
1353
  return this.callProtocolV1(uuid, name, data, options);
1289
1354
  });
1290
1355
  }
1356
+ post(uuid, name, data) {
1357
+ return __awaiter(this, void 0, void 0, function* () {
1358
+ if (this.deviceProtocol.get(uuid) === 'V2') {
1359
+ yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
1360
+ return;
1361
+ }
1362
+ yield this.callProtocolV1(uuid, name, data);
1363
+ });
1364
+ }
1291
1365
  callProtocolV1(uuid, name, data, options) {
1292
1366
  var _a, _b;
1293
1367
  return __awaiter(this, void 0, void 0, function* () {
@@ -1301,6 +1375,7 @@ class ElectronBleTransport {
1301
1375
  const runPromise = hdShared.createDeferred();
1302
1376
  runPromise.promise.catch(() => undefined);
1303
1377
  this.runPromise = runPromise;
1378
+ this.runPromiseDeviceId = uuid;
1304
1379
  const messages = this._messages;
1305
1380
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1306
1381
  let timeout;
@@ -1339,6 +1414,17 @@ class ElectronBleTransport {
1339
1414
  }
1340
1415
  catch (e) {
1341
1416
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1417
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1418
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1419
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1420
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1421
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1422
+ this.notificationCleanups.delete(uuid);
1423
+ this.notificationTokens.delete(uuid);
1424
+ if (!isProbeTimeout) {
1425
+ yield this.releaseNative(uuid);
1426
+ }
1427
+ }
1342
1428
  throw e;
1343
1429
  }
1344
1430
  finally {
@@ -1346,22 +1432,23 @@ class ElectronBleTransport {
1346
1432
  clearTimeout(timeout);
1347
1433
  if (this.runPromise === runPromise) {
1348
1434
  this.runPromise = null;
1435
+ this.runPromiseDeviceId = null;
1349
1436
  }
1350
1437
  }
1351
1438
  });
1352
1439
  }
1353
1440
  callProtocolV2(uuid, name, data, options) {
1354
- var _a, _b;
1441
+ var _a;
1355
1442
  return __awaiter(this, void 0, void 0, function* () {
1356
1443
  if (!this._messages || !this._messagesV2) {
1357
1444
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1358
1445
  }
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 });
1446
+ const callOptions = options;
1360
1447
  try {
1361
1448
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1362
1449
  }
1363
1450
  catch (e) {
1364
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1451
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1365
1452
  throw e;
1366
1453
  }
1367
1454
  });
@@ -1376,6 +1463,7 @@ class ElectronBleTransport {
1376
1463
  };
1377
1464
  return {
1378
1465
  router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1466
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1379
1467
  generation,
1380
1468
  prepareCall: () => {
1381
1469
  var _a;
@@ -1383,10 +1471,7 @@ class ElectronBleTransport {
1383
1471
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1384
1472
  this.resetProtocolV2Frames(uuid);
1385
1473
  },
1386
- writeFrame: (frame) => {
1387
- assertCurrentGeneration();
1388
- return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1389
- },
1474
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1390
1475
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1391
1476
  assertCurrentGeneration();
1392
1477
  const rxFrame = yield this.readProtocolV2Frame(uuid);
@@ -1406,6 +1491,7 @@ class ElectronBleTransport {
1406
1491
  };
1407
1492
  }
1408
1493
  processProtocolV1Notification(deviceId, hexData) {
1494
+ var _a;
1409
1495
  try {
1410
1496
  if (typeof hexData !== 'string') {
1411
1497
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1424,6 +1510,10 @@ class ElectronBleTransport {
1424
1510
  bufferState.buffer = [...data.subarray(3)];
1425
1511
  }
1426
1512
  else {
1513
+ if (bufferState.buffer.length === 0) {
1514
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
1515
+ return { isComplete: false };
1516
+ }
1427
1517
  bufferState.buffer = bufferState.buffer.concat([...data]);
1428
1518
  }
1429
1519
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {