@onekeyfe/hd-transport-web-device 1.2.0-alpha.11 → 1.2.0-alpha.110

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
@@ -51,33 +51,22 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
51
51
  const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
52
52
  const PACKET_IO_MAX_RETRIES = 3;
53
53
  const PACKET_IO_RETRY_DELAY = 300;
54
- const PROTOCOL_PROBE_TIMEOUT = 1000;
55
- const WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
56
- function shouldSuppressWebUsbCallLog(name) {
57
- const normalized = name.replace(/[_\s-]/g, '');
58
- return WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
59
- }
60
- function isLogBlockCommand$1(name) {
61
- var _a, _b;
62
- return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
63
- }
64
- function shouldBlockWebUsbCallDataLog(name) {
65
- const normalized = name.replace(/[_\s-]/g, '');
66
- return isLogBlockCommand$1(name) || WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
67
- }
54
+ const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
55
+ const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
56
+ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
68
57
  function inferProtocolHintFromDeviceName$1(name) {
69
58
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
70
59
  }
71
- class WebUsbTransport {
60
+ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
72
61
  constructor() {
62
+ super({
63
+ router: transport.PROTOCOL_V2_CHANNEL_USB,
64
+ maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
65
+ logPrefix: 'ProtocolV2 WebUSB',
66
+ });
73
67
  this.deviceProtocol = new Map();
74
68
  this.deviceProtocolHints = new Map();
75
- this.protocolV2Assemblers = new Map();
76
- this.protocolV2Sessions = new Map();
77
- this.protocolV2ReadTimeouts = new Map();
78
69
  this.deviceEndpoints = new Map();
79
- this.mockSerialPaths = new WeakMap();
80
- this.mockSerialCounter = 0;
81
70
  this.name = 'WebUsbTransport';
82
71
  this.stopped = false;
83
72
  this.configured = false;
@@ -101,10 +90,17 @@ class WebUsbTransport {
101
90
  }
102
91
  configureProtocolV2(signedData) {
103
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;
104
99
  this.messagesV2 = parseConfigure$1(signedData);
105
- this.protocolV2Sessions.clear();
106
- this.protocolV2ReadTimeouts.clear();
107
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] Protocol V2 schema configured');
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); });
108
104
  }
109
105
  promptDeviceAccess() {
110
106
  return __awaiter(this, void 0, void 0, function* () {
@@ -126,28 +122,18 @@ class WebUsbTransport {
126
122
  return this.deviceList;
127
123
  });
128
124
  }
129
- getDevicePath(device) {
130
- var _a;
131
- if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
132
- return device.serialNumber;
133
- }
134
- let path = this.mockSerialPaths.get(device);
135
- if (!path) {
136
- this.mockSerialCounter += 1;
137
- path = `mock-serial:${device.vendorId.toString(16)}:${device.productId.toString(16)}:${this.mockSerialCounter}`;
138
- this.mockSerialPaths.set(device, path);
139
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUSB] device has no serial number, using mock path: ${path}`);
140
- }
141
- return path;
142
- }
143
125
  getConnectedDevices() {
144
126
  return __awaiter(this, void 0, void 0, function* () {
145
127
  if (!this.usb)
146
128
  return [];
147
129
  const devices = yield this.usb.getDevices();
148
- 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
+ });
149
135
  this.deviceList = onekeyDevices.map(device => {
150
- const path = this.getDevicePath(device);
136
+ const path = device.serialNumber;
151
137
  const protocolHint = inferProtocolHintFromDeviceName$1(device.productName);
152
138
  if (protocolHint) {
153
139
  this.deviceProtocolHints.set(path, protocolHint);
@@ -158,25 +144,22 @@ class WebUsbTransport {
158
144
  commType: 'webusb',
159
145
  };
160
146
  });
161
- for (const dev of onekeyDevices) {
162
- this.Log.debug(`[WebUSB] Device: name="${dev.productName}" serial="${dev.serialNumber}" ` +
163
- `VID=0x${dev.vendorId.toString(16)} PID=0x${dev.productId.toString(16)}`);
164
- }
165
147
  return this.deviceList;
166
148
  });
167
149
  }
168
150
  acquire(input) {
169
- var _a, _b, _c;
151
+ var _a, _b, _c, _d;
170
152
  return __awaiter(this, void 0, void 0, function* () {
171
153
  if (!input.path)
172
154
  return;
173
155
  try {
156
+ yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
174
157
  yield this.closeOpenDevice(input.path);
175
158
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
176
159
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
177
160
  const protocolHint = input.expectedProtocol
178
161
  ? undefined
179
- : (_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);
180
163
  if (protocolHint) {
181
164
  this.deviceProtocolHints.set(input.path, protocolHint);
182
165
  }
@@ -185,6 +168,7 @@ class WebUsbTransport {
185
168
  }
186
169
  catch (e) {
187
170
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
171
+ yield this.closeOpenDevice(input.path);
188
172
  throw e;
189
173
  }
190
174
  });
@@ -192,32 +176,53 @@ class WebUsbTransport {
192
176
  createProtocolMismatchError(expected) {
193
177
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
194
178
  }
179
+ createProtocolProbeTimeoutError(expected, attempts) {
180
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInitializeFailed, `Protocol ${expected} probe timeout after ${attempts} attempts`);
181
+ }
195
182
  createProtocolDetectionError() {
196
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'Unable to detect USB protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
183
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
197
184
  }
198
185
  detectProtocol(path, expectedProtocol, protocolHint) {
186
+ var _a;
199
187
  return __awaiter(this, void 0, void 0, function* () {
200
188
  if (expectedProtocol === 'V1') {
201
189
  if (yield this.probeProtocolV1(path)) {
202
190
  this.deviceProtocol.set(path, 'V1');
203
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
204
191
  return 'V1';
205
192
  }
193
+ yield this.closeConnectionAfterProbe(path);
206
194
  throw this.createProtocolMismatchError(expectedProtocol);
207
195
  }
208
196
  if (expectedProtocol === 'V2') {
209
- this.deviceProtocol.set(path, 'V2');
210
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
211
- 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);
212
212
  }
213
213
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
214
- for (const protocol of probeOrder) {
214
+ for (const [index, protocol] of probeOrder.entries()) {
215
215
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
216
216
  if (detected) {
217
217
  this.deviceProtocol.set(path, protocol);
218
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
219
218
  return protocol;
220
219
  }
220
+ if (index < probeOrder.length - 1) {
221
+ yield this.resetConnectionAfterProbe(path);
222
+ }
223
+ else {
224
+ yield this.closeConnectionAfterProbe(path);
225
+ }
221
226
  }
222
227
  this.deviceProtocol.delete(path);
223
228
  throw this.createProtocolDetectionError();
@@ -284,10 +289,9 @@ class WebUsbTransport {
284
289
  };
285
290
  }
286
291
  connectToDevice(path, first) {
287
- var _a, _b;
292
+ var _a;
288
293
  return __awaiter(this, void 0, void 0, function* () {
289
294
  let device = yield this.findDevice(path);
290
- this.Log.debug('[WebUsbTransport] connecting to device:', device.productName, 'PID:', device.productId);
291
295
  if (!device.opened) {
292
296
  yield device.open();
293
297
  }
@@ -309,33 +313,30 @@ class WebUsbTransport {
309
313
  }
310
314
  const endpoints = this.discoverEndpoints(device);
311
315
  this.deviceEndpoints.set(path, endpoints);
312
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
313
- this.protocolV2Assemblers.set(path, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES));
314
316
  yield device.claimInterface(endpoints.interfaceNumber);
315
317
  yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
316
318
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
317
319
  });
318
320
  }
319
321
  closeOpenDevice(path) {
320
- var _a, _b, _c, _d, _e;
322
+ var _a, _b, _c, _d;
321
323
  return __awaiter(this, void 0, void 0, function* () {
322
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
323
- const current = (_b = this.deviceList.find(device => device.path === path)) === null || _b === void 0 ? void 0 : _b.device;
324
+ const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
324
325
  if (!(current === null || current === void 0 ? void 0 : current.opened))
325
326
  return;
326
327
  const endpoints = this.deviceEndpoints.get(path);
327
- const ifaceNum = (_c = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _c !== void 0 ? _c : this.interfaceId;
328
+ const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
328
329
  try {
329
330
  yield current.releaseInterface(ifaceNum);
330
331
  }
331
332
  catch (error) {
332
- (_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);
333
334
  }
334
335
  try {
335
336
  yield current.close();
336
337
  }
337
338
  catch (error) {
338
- (_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);
339
340
  }
340
341
  });
341
342
  }
@@ -497,29 +498,28 @@ class WebUsbTransport {
497
498
  throw lastError;
498
499
  });
499
500
  }
500
- resetConnectionAfterProbe(path) {
501
- var _a, _b;
501
+ transferInOnce(path, length) {
502
+ var _a;
502
503
  return __awaiter(this, void 0, void 0, function* () {
503
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
504
- this.protocolV2Sessions.delete(path);
505
- this.protocolV2ReadTimeouts.delete(path);
506
- try {
507
- const device = yield this.findDevice(path);
508
- if (device.opened) {
509
- const endpoints = this.deviceEndpoints.get(path);
510
- const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
511
- try {
512
- yield device.releaseInterface(ifaceNum);
513
- }
514
- catch (error) {
515
- this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
516
- }
517
- yield device.close();
518
- }
519
- }
520
- catch (error) {
521
- 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');
522
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);
523
523
  yield this.getConnectedDevices();
524
524
  yield this.connect(path, false);
525
525
  });
@@ -542,6 +542,14 @@ class WebUsbTransport {
542
542
  timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
543
543
  timedOut = true;
544
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
+ }
545
553
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
546
554
  }), timeoutMs);
547
555
  }),
@@ -559,11 +567,12 @@ class WebUsbTransport {
559
567
  return false;
560
568
  }
561
569
  try {
562
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
570
+ yield this.callProtocolV1(path, 'Initialize', {}, {
571
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
572
+ });
563
573
  return true;
564
574
  }
565
- catch (error) {
566
- this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
575
+ catch (_error) {
567
576
  return false;
568
577
  }
569
578
  });
@@ -575,10 +584,9 @@ class WebUsbTransport {
575
584
  }
576
585
  return transport.probeProtocolV2({
577
586
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
578
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
587
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
579
588
  logger: this.Log,
580
589
  logPrefix: 'ProtocolV2 WebUSB',
581
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
582
590
  });
583
591
  });
584
592
  }
@@ -595,13 +603,6 @@ class WebUsbTransport {
595
603
  if (!protocol) {
596
604
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
597
605
  }
598
- if (shouldSuppressWebUsbCallLog(name)) ;
599
- else if (shouldBlockWebUsbCallDataLog(name)) {
600
- this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
601
- }
602
- else {
603
- this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
604
- }
605
606
  if (protocol === 'V2') {
606
607
  return this.callProtocolV2(path, name, data, options);
607
608
  }
@@ -630,67 +631,8 @@ class WebUsbTransport {
630
631
  });
631
632
  }
632
633
  callProtocolV2(path, name, data, options) {
633
- var _a;
634
634
  return __awaiter(this, void 0, void 0, function* () {
635
- const protocolV1Messages = this.messages;
636
- if (!this.messagesV2) {
637
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
638
- }
639
- if (!protocolV1Messages) {
640
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
641
- }
642
- let session = this.protocolV2Sessions.get(path);
643
- if (!session) {
644
- session = new transport.ProtocolV2Session({
645
- schemas: {
646
- protocolV1: protocolV1Messages,
647
- protocolV2: this.messagesV2,
648
- },
649
- router: transport.PROTOCOL_V2_CHANNEL_USB,
650
- writeFrame: (frame) => this.transferOutOnce(path, frame),
651
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
652
- logger: this.Log,
653
- logPrefix: 'ProtocolV2 WebUSB',
654
- createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
655
- });
656
- this.protocolV2Sessions.set(path, session);
657
- }
658
- this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
659
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
660
- try {
661
- return yield session.call(name, data, options);
662
- }
663
- finally {
664
- this.protocolV2ReadTimeouts.delete(path);
665
- }
666
- });
667
- }
668
- receiveProtocolV2Frame(path, timeoutMs) {
669
- return __awaiter(this, void 0, void 0, function* () {
670
- let assembler = this.protocolV2Assemblers.get(path);
671
- if (!assembler) {
672
- assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
673
- this.protocolV2Assemblers.set(path, assembler);
674
- }
675
- let frame = assembler.push(new Uint8Array(0));
676
- const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
677
- while (!frame) {
678
- const cancelToken = { cancelled: false };
679
- const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
680
- const dataView = deadline
681
- ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
682
- cancelToken.cancelled = true;
683
- })
684
- : yield transferIn;
685
- const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
686
- try {
687
- frame = assembler.push(bytes);
688
- }
689
- catch (error) {
690
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
691
- }
692
- }
693
- return frame;
635
+ return this.callProtocolV2Usb(path, name, data, options);
694
636
  });
695
637
  }
696
638
  receiveData(path, timeoutMs) {
@@ -731,42 +673,71 @@ class WebUsbTransport {
731
673
  });
732
674
  }
733
675
  release(path) {
734
- var _a, _b;
735
676
  return __awaiter(this, void 0, void 0, function* () {
736
- const device = yield this.findDevice(path);
737
- const endpoints = this.deviceEndpoints.get(path);
738
- const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
739
- yield device.releaseInterface(ifaceNum);
740
- yield device.close();
677
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
678
+ yield this.closeOpenDevice(path);
741
679
  this.deviceProtocol.delete(path);
742
680
  this.deviceProtocolHints.delete(path);
743
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
744
- this.protocolV2Assemblers.delete(path);
745
681
  this.deviceEndpoints.delete(path);
746
682
  });
747
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
+ }
748
720
  getProtocolType(path) {
749
721
  return this.deviceProtocol.get(path);
750
722
  }
751
723
  }
752
724
 
753
- const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
754
- function shouldSuppressHighVolumeCallLog(name) {
755
- const normalized = name.replace(/[_\s-]/g, '');
756
- return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
757
- }
758
- function isLogBlockCommand(name) {
759
- var _a, _b;
760
- return (_b = (_a = transport.LogBlockCommand === null || transport.LogBlockCommand === void 0 ? void 0 : transport.LogBlockCommand.has) === null || _a === void 0 ? void 0 : _a.call(transport.LogBlockCommand, name)) !== null && _b !== void 0 ? _b : false;
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);
761
731
  }
732
+
762
733
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
763
734
  function inferProtocolHintFromDeviceName(name) {
764
735
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
765
736
  }
766
737
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
767
- const BLE_PACKET_SIZE = 192;
738
+ const BLE_PACKET_SIZE_FALLBACK = 192;
739
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
768
740
  const BLE_WRITE_DELAY_MS = 5;
769
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
770
741
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
771
742
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
772
743
  class ElectronBleTransport {
@@ -774,9 +745,12 @@ class ElectronBleTransport {
774
745
  this.name = 'ElectronBleTransport';
775
746
  this.configured = false;
776
747
  this.runPromise = null;
748
+ this.runPromiseDeviceId = null;
777
749
  this.connectedDevices = new Set();
778
750
  this.deviceProtocol = new Map();
779
751
  this.deviceProtocolHints = new Map();
752
+ this.deviceMtus = new Map();
753
+ this.devicePacketCapacities = new Map();
780
754
  this.v1Buffers = new Map();
781
755
  this.v2Assemblers = new Map();
782
756
  this.v2FrameQueues = new Map();
@@ -798,11 +772,13 @@ class ElectronBleTransport {
798
772
  this.rejectProtocolV2Frames(uuid, new Error(reason));
799
773
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
800
774
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
801
- yield this.release(uuid);
775
+ yield this.releaseNative(uuid);
802
776
  }
803
777
  }),
804
778
  });
779
+ this.warnedMissingRelease = false;
805
780
  this.notificationCleanups = new Map();
781
+ this.mtuCleanups = new Map();
806
782
  this.disconnectCleanups = new Map();
807
783
  this.notificationTokens = new Map();
808
784
  this.nextNotificationToken = 1;
@@ -842,15 +818,27 @@ class ElectronBleTransport {
842
818
  .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
843
819
  this.connectedDevices.delete(deviceId);
844
820
  this.deviceProtocol.delete(deviceId);
821
+ this.deviceMtus.delete(deviceId);
822
+ this.devicePacketCapacities.delete(deviceId);
845
823
  this.v1Buffers.delete(deviceId);
846
824
  this.v2Assemblers.delete(deviceId);
847
825
  this.resetProtocolV2Frames(deviceId);
848
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
+ }
849
832
  const notifyCleanup = this.notificationCleanups.get(deviceId);
850
833
  if (notifyCleanup) {
851
834
  notifyCleanup();
852
835
  this.notificationCleanups.delete(deviceId);
853
836
  }
837
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
838
+ if (mtuCleanup) {
839
+ mtuCleanup();
840
+ this.mtuCleanups.delete(deviceId);
841
+ }
854
842
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
855
843
  if (disconnectCleanup) {
856
844
  disconnectCleanup();
@@ -871,12 +859,18 @@ class ElectronBleTransport {
871
859
  this.configured = true;
872
860
  }
873
861
  configureProtocolV2(signedData) {
874
- var _a;
862
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
863
+ if (this.protocolV2SchemaConfiguration === configuration) {
864
+ return;
865
+ }
866
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
875
867
  this._messagesV2 = parseConfigure(signedData);
876
- this.protocolV2Links
877
- .invalidateAllLinks('Protocol V2 schema reconfigured')
878
- .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
879
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V2 schema configured');
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
+ }
880
874
  }
881
875
  listen() {
882
876
  return __awaiter(this, void 0, void 0, function* () {
@@ -908,7 +902,7 @@ class ElectronBleTransport {
908
902
  });
909
903
  }
910
904
  acquire(input) {
911
- var _a, _b, _c, _d, _e, _f;
905
+ var _a, _b, _c, _d, _e, _f, _g, _h;
912
906
  return __awaiter(this, void 0, void 0, function* () {
913
907
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
914
908
  if (!uuid) {
@@ -920,8 +914,8 @@ class ElectronBleTransport {
920
914
  if (forceCleanRunPromise && this.runPromise) {
921
915
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
922
916
  this.runPromise.reject(error);
923
- this.rejectAllProtocolV2Frames(error);
924
917
  this.runPromise = null;
918
+ this.runPromiseDeviceId = null;
925
919
  }
926
920
  try {
927
921
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -933,7 +927,7 @@ class ElectronBleTransport {
933
927
  }
934
928
  const protocolHint = expectedProtocol
935
929
  ? undefined
936
- : (_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);
937
931
  if (protocolHint) {
938
932
  this.deviceProtocolHints.set(uuid, protocolHint);
939
933
  }
@@ -944,16 +938,31 @@ class ElectronBleTransport {
944
938
  catch (error) {
945
939
  this.handleBluetoothError(error);
946
940
  }
941
+ const mtuCleanup = this.createMtuSubscription(uuid);
942
+ if (mtuCleanup) {
943
+ this.mtuCleanups.set(uuid, mtuCleanup);
944
+ }
947
945
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
948
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
946
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
949
947
  yield window.desktopApi.nobleBle.subscribe(uuid);
948
+ yield this.refreshBlePacketCapacity(uuid);
950
949
  const cleanup = this.createNotificationSubscription(uuid);
951
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
+ }
952
960
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
953
961
  var _a;
954
- if (disconnectedDevice.id === uuid) {
962
+ if (disconnectedDevice.id === uuid &&
963
+ this.notificationTokens.get(uuid) === connectionToken) {
955
964
  this.cleanupDeviceState(uuid);
956
- (_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, {
957
966
  name: disconnectedDevice.name,
958
967
  id: disconnectedDevice.id,
959
968
  connectId: disconnectedDevice.id,
@@ -961,35 +970,46 @@ class ElectronBleTransport {
961
970
  }
962
971
  });
963
972
  this.disconnectCleanups.set(uuid, disconnectCleanup);
964
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
965
- (_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
966
- name: device.name,
967
- id: device.id,
968
- connectId: device.id,
969
- });
970
973
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
971
974
  }
972
975
  catch (error) {
973
- (_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);
974
977
  try {
975
- 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)) {
976
979
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
977
980
  yield window.desktopApi.nobleBle.disconnect(uuid);
978
981
  }
979
982
  }
980
983
  catch (cleanupError) {
981
- (_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);
982
985
  }
983
986
  this.cleanupDeviceState(uuid);
984
987
  throw error;
985
988
  }
986
989
  });
987
990
  }
988
- release(id) {
989
- var _a, _b;
991
+ release(id, _onclose, keepSession) {
992
+ var _a;
990
993
  return __awaiter(this, void 0, void 0, function* () {
991
994
  try {
992
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 {
993
1013
  if (this.connectedDevices.has(id)) {
994
1014
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
995
1015
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -1004,11 +1024,35 @@ class ElectronBleTransport {
1004
1024
  }
1005
1025
  });
1006
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
+ }
1007
1051
  createProtocolMismatchError(expected) {
1008
1052
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1009
1053
  }
1010
1054
  createProtocolDetectionError() {
1011
- 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');
1012
1056
  }
1013
1057
  clearProbeProtocol(uuid, protocol) {
1014
1058
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1027,9 +1071,12 @@ class ElectronBleTransport {
1027
1071
  throw this.createProtocolMismatchError(expectedProtocol);
1028
1072
  }
1029
1073
  if (expectedProtocol === 'V2') {
1030
- this.deviceProtocol.set(uuid, 'V2');
1031
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1032
- 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);
1033
1080
  }
1034
1081
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1035
1082
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1063,16 +1110,17 @@ class ElectronBleTransport {
1063
1110
  });
1064
1111
  }
1065
1112
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1066
- var _a, _b, _c, _d, _e, _f, _g;
1113
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1067
1114
  return __awaiter(this, void 0, void 0, function* () {
1068
1115
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1069
1116
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1070
1117
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1071
1118
  this.resetProtocolV2Frames(uuid);
1072
- if (this.runPromise) {
1119
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1073
1120
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1074
1121
  this.runPromise.reject(error);
1075
1122
  this.runPromise = null;
1123
+ this.runPromiseDeviceId = null;
1076
1124
  }
1077
1125
  const notifyCleanup = this.notificationCleanups.get(uuid);
1078
1126
  if (notifyCleanup) {
@@ -1087,10 +1135,20 @@ class ElectronBleTransport {
1087
1135
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1088
1136
  }
1089
1137
  try {
1090
- 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));
1139
+ }
1140
+ catch (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);
1091
1149
  }
1092
1150
  catch (error) {
1093
- (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1151
+ (_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
1094
1152
  throw error;
1095
1153
  }
1096
1154
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1105,12 +1163,12 @@ class ElectronBleTransport {
1105
1163
  }
1106
1164
  try {
1107
1165
  this.deviceProtocol.set(uuid, 'V1');
1108
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1166
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1109
1167
  return true;
1110
1168
  }
1111
1169
  catch (error) {
1112
1170
  this.clearProbeProtocol(uuid, 'V1');
1113
- (_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);
1114
1172
  return false;
1115
1173
  }
1116
1174
  });
@@ -1140,25 +1198,6 @@ class ElectronBleTransport {
1140
1198
  return detected;
1141
1199
  });
1142
1200
  }
1143
- writeWithChunking(uuid, hexData) {
1144
- return __awaiter(this, void 0, void 0, function* () {
1145
- const totalBytes = hexData.length / 2;
1146
- if (totalBytes <= BLE_PACKET_SIZE) {
1147
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1148
- yield this.writeOnce(uuid, hexData);
1149
- return;
1150
- }
1151
- for (let offset = 0; offset < hexData.length;) {
1152
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1153
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1154
- offset += chunkHexLen;
1155
- yield this.writeOnce(uuid, chunkHex);
1156
- if (offset < hexData.length) {
1157
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1158
- }
1159
- }
1160
- });
1161
- }
1162
1201
  writeOnce(uuid, hexData) {
1163
1202
  var _a;
1164
1203
  return __awaiter(this, void 0, void 0, function* () {
@@ -1166,7 +1205,51 @@ class ElectronBleTransport {
1166
1205
  if (!nobleBle) {
1167
1206
  throw new Error('Noble BLE API not available');
1168
1207
  }
1169
- 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)),
1170
1253
  });
1171
1254
  }
1172
1255
  handleNotification(deviceId, hexData) {
@@ -1177,7 +1260,7 @@ class ElectronBleTransport {
1177
1260
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1178
1261
  this.rejectProtocolV2Frames(deviceId, error);
1179
1262
  }
1180
- else if (this.runPromise) {
1263
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1181
1264
  this.runPromise.reject(error);
1182
1265
  }
1183
1266
  return;
@@ -1229,16 +1312,8 @@ class ElectronBleTransport {
1229
1312
  }
1230
1313
  this.getProtocolV2FrameQueue(uuid).push(frame);
1231
1314
  }
1232
- rejectAllProtocolV2Frames(error) {
1233
- this.v2FrameQueues.clear();
1234
- for (const framePromise of this.v2FramePromises.values()) {
1235
- framePromise.reject(error);
1236
- }
1237
- this.v2FramePromises.clear();
1238
- }
1239
1315
  resetProtocolV2Frames(uuid) {
1240
- this.v2FrameQueues.delete(uuid);
1241
- this.v2FramePromises.delete(uuid);
1316
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1242
1317
  }
1243
1318
  rejectProtocolV2Frames(uuid, error) {
1244
1319
  this.v2FrameQueues.delete(uuid);
@@ -1271,17 +1346,19 @@ class ElectronBleTransport {
1271
1346
  const result = this.processProtocolV1Notification(deviceId, hexData);
1272
1347
  if (result.error) {
1273
1348
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1274
- if (this.runPromise) {
1349
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1275
1350
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1276
1351
  }
1277
1352
  return;
1278
1353
  }
1279
- if (result.isComplete && result.completePacket && this.runPromise) {
1354
+ if (result.isComplete &&
1355
+ result.completePacket &&
1356
+ this.runPromise &&
1357
+ this.runPromiseDeviceId === deviceId) {
1280
1358
  this.runPromise.resolve(result.completePacket);
1281
1359
  }
1282
1360
  }
1283
1361
  call(uuid, name, data, options) {
1284
- var _a, _b;
1285
1362
  return __awaiter(this, void 0, void 0, function* () {
1286
1363
  if (!this._messages) {
1287
1364
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1293,13 +1370,6 @@ class ElectronBleTransport {
1293
1370
  if (!protocol) {
1294
1371
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1295
1372
  }
1296
- if (shouldSuppressHighVolumeCallLog(name)) ;
1297
- else if (isLogBlockCommand(name)) {
1298
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
1299
- }
1300
- else {
1301
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
1302
- }
1303
1373
  if (protocol === 'V2') {
1304
1374
  return this.callProtocolV2(uuid, name, data, options);
1305
1375
  }
@@ -1319,6 +1389,7 @@ class ElectronBleTransport {
1319
1389
  const runPromise = hdShared.createDeferred();
1320
1390
  runPromise.promise.catch(() => undefined);
1321
1391
  this.runPromise = runPromise;
1392
+ this.runPromiseDeviceId = uuid;
1322
1393
  const messages = this._messages;
1323
1394
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1324
1395
  let timeout;
@@ -1357,6 +1428,17 @@ class ElectronBleTransport {
1357
1428
  }
1358
1429
  catch (e) {
1359
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
+ }
1360
1442
  throw e;
1361
1443
  }
1362
1444
  finally {
@@ -1364,22 +1446,23 @@ class ElectronBleTransport {
1364
1446
  clearTimeout(timeout);
1365
1447
  if (this.runPromise === runPromise) {
1366
1448
  this.runPromise = null;
1449
+ this.runPromiseDeviceId = null;
1367
1450
  }
1368
1451
  }
1369
1452
  });
1370
1453
  }
1371
1454
  callProtocolV2(uuid, name, data, options) {
1372
- var _a, _b;
1455
+ var _a;
1373
1456
  return __awaiter(this, void 0, void 0, function* () {
1374
1457
  if (!this._messages || !this._messagesV2) {
1375
1458
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1376
1459
  }
1377
- 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;
1378
1461
  try {
1379
1462
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1380
1463
  }
1381
1464
  catch (e) {
1382
- (_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);
1383
1466
  throw e;
1384
1467
  }
1385
1468
  });
@@ -1394,6 +1477,7 @@ class ElectronBleTransport {
1394
1477
  };
1395
1478
  return {
1396
1479
  router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1480
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1397
1481
  generation,
1398
1482
  prepareCall: () => {
1399
1483
  var _a;
@@ -1401,10 +1485,7 @@ class ElectronBleTransport {
1401
1485
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1402
1486
  this.resetProtocolV2Frames(uuid);
1403
1487
  },
1404
- writeFrame: (frame) => {
1405
- assertCurrentGeneration();
1406
- return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1407
- },
1488
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1408
1489
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1409
1490
  assertCurrentGeneration();
1410
1491
  const rxFrame = yield this.readProtocolV2Frame(uuid);
@@ -1424,6 +1505,7 @@ class ElectronBleTransport {
1424
1505
  };
1425
1506
  }
1426
1507
  processProtocolV1Notification(deviceId, hexData) {
1508
+ var _a;
1427
1509
  try {
1428
1510
  if (typeof hexData !== 'string') {
1429
1511
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1442,6 +1524,10 @@ class ElectronBleTransport {
1442
1524
  bufferState.buffer = [...data.subarray(3)];
1443
1525
  }
1444
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
+ }
1445
1531
  bufferState.buffer = bufferState.buffer.concat([...data]);
1446
1532
  }
1447
1533
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {