@onekeyfe/hd-transport-web-device 1.2.0-alpha.9 → 1.2.0-alpha.90

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.RuntimeError, `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,12 +603,8 @@ 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);
606
+ if (!transport.shouldSuppressHighVolumeCallLog(name)) {
607
+ this.Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
604
608
  }
605
609
  if (protocol === 'V2') {
606
610
  return this.callProtocolV2(path, name, data, options);
@@ -630,67 +634,8 @@ class WebUsbTransport {
630
634
  });
631
635
  }
632
636
  callProtocolV2(path, name, data, options) {
633
- var _a;
634
637
  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;
638
+ return this.callProtocolV2Usb(path, name, data, options);
694
639
  });
695
640
  }
696
641
  receiveData(path, timeoutMs) {
@@ -731,42 +676,71 @@ class WebUsbTransport {
731
676
  });
732
677
  }
733
678
  release(path) {
734
- var _a, _b;
735
679
  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();
680
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
681
+ yield this.closeOpenDevice(path);
741
682
  this.deviceProtocol.delete(path);
742
683
  this.deviceProtocolHints.delete(path);
743
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
744
- this.protocolV2Assemblers.delete(path);
745
684
  this.deviceEndpoints.delete(path);
746
685
  });
747
686
  }
687
+ getProtocolV2UsbSchemas() {
688
+ if (!this.messages || !this.messagesV2) {
689
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
690
+ }
691
+ return {
692
+ protocolV1: this.messages,
693
+ protocolV2: this.messagesV2,
694
+ };
695
+ }
696
+ getProtocolV2UsbLogger() {
697
+ return this.Log;
698
+ }
699
+ writeProtocolV2UsbPacket(path, frame, _context) {
700
+ return __awaiter(this, void 0, void 0, function* () {
701
+ yield this.transferOutOnce(path, frame);
702
+ });
703
+ }
704
+ readProtocolV2UsbPacket(path, _context) {
705
+ return __awaiter(this, void 0, void 0, function* () {
706
+ const dataView = yield this.transferInOnce(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES);
707
+ return new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
708
+ });
709
+ }
710
+ resetProtocolV2UsbNativeLink(path, _reason) {
711
+ return __awaiter(this, void 0, void 0, function* () {
712
+ yield this.closeOpenDevice(path);
713
+ });
714
+ }
715
+ onProtocolV2UsbLinkInvalidated(path, reason) {
716
+ var _a;
717
+ this.deviceProtocol.delete(path);
718
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
719
+ }
720
+ createProtocolV2UsbTimeoutError(name, timeoutMs) {
721
+ return new transport.ProtocolV2LinkError('response-timeout', `Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
722
+ }
748
723
  getProtocolType(path) {
749
724
  return this.deviceProtocol.get(path);
750
725
  }
751
726
  }
752
727
 
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;
728
+ const BLE_ATT_HEADER_BYTES = 3;
729
+ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapacity) {
730
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= BLE_ATT_HEADER_BYTES) {
731
+ return fallbackPacketCapacity;
732
+ }
733
+ return Math.min(maximumPacketCapacity, Math.floor(mtu) - BLE_ATT_HEADER_BYTES);
761
734
  }
735
+
762
736
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
763
737
  function inferProtocolHintFromDeviceName(name) {
764
738
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
765
739
  }
766
740
  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;
741
+ const BLE_PACKET_SIZE_FALLBACK = 192;
742
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
768
743
  const BLE_WRITE_DELAY_MS = 5;
769
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
770
744
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
771
745
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
772
746
  class ElectronBleTransport {
@@ -774,9 +748,12 @@ class ElectronBleTransport {
774
748
  this.name = 'ElectronBleTransport';
775
749
  this.configured = false;
776
750
  this.runPromise = null;
751
+ this.runPromiseDeviceId = null;
777
752
  this.connectedDevices = new Set();
778
753
  this.deviceProtocol = new Map();
779
754
  this.deviceProtocolHints = new Map();
755
+ this.deviceMtus = new Map();
756
+ this.devicePacketCapacities = new Map();
780
757
  this.v1Buffers = new Map();
781
758
  this.v2Assemblers = new Map();
782
759
  this.v2FrameQueues = new Map();
@@ -798,11 +775,13 @@ class ElectronBleTransport {
798
775
  this.rejectProtocolV2Frames(uuid, new Error(reason));
799
776
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
800
777
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
801
- yield this.release(uuid);
778
+ yield this.releaseNative(uuid);
802
779
  }
803
780
  }),
804
781
  });
782
+ this.warnedMissingRelease = false;
805
783
  this.notificationCleanups = new Map();
784
+ this.mtuCleanups = new Map();
806
785
  this.disconnectCleanups = new Map();
807
786
  this.notificationTokens = new Map();
808
787
  this.nextNotificationToken = 1;
@@ -842,15 +821,27 @@ class ElectronBleTransport {
842
821
  .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
843
822
  this.connectedDevices.delete(deviceId);
844
823
  this.deviceProtocol.delete(deviceId);
824
+ this.deviceMtus.delete(deviceId);
825
+ this.devicePacketCapacities.delete(deviceId);
845
826
  this.v1Buffers.delete(deviceId);
846
827
  this.v2Assemblers.delete(deviceId);
847
828
  this.resetProtocolV2Frames(deviceId);
848
829
  this.notificationTokens.delete(deviceId);
830
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
831
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
832
+ this.runPromise = null;
833
+ this.runPromiseDeviceId = null;
834
+ }
849
835
  const notifyCleanup = this.notificationCleanups.get(deviceId);
850
836
  if (notifyCleanup) {
851
837
  notifyCleanup();
852
838
  this.notificationCleanups.delete(deviceId);
853
839
  }
840
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
841
+ if (mtuCleanup) {
842
+ mtuCleanup();
843
+ this.mtuCleanups.delete(deviceId);
844
+ }
854
845
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
855
846
  if (disconnectCleanup) {
856
847
  disconnectCleanup();
@@ -871,12 +862,18 @@ class ElectronBleTransport {
871
862
  this.configured = true;
872
863
  }
873
864
  configureProtocolV2(signedData) {
874
- var _a;
865
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
866
+ if (this.protocolV2SchemaConfiguration === configuration) {
867
+ return;
868
+ }
869
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
875
870
  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');
871
+ this.protocolV2SchemaConfiguration = configuration;
872
+ if (isReconfiguration) {
873
+ this.protocolV2Links
874
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
875
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
876
+ }
880
877
  }
881
878
  listen() {
882
879
  return __awaiter(this, void 0, void 0, function* () {
@@ -908,7 +905,7 @@ class ElectronBleTransport {
908
905
  });
909
906
  }
910
907
  acquire(input) {
911
- var _a, _b, _c, _d, _e, _f;
908
+ var _a, _b, _c, _d, _e, _f, _g, _h;
912
909
  return __awaiter(this, void 0, void 0, function* () {
913
910
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
914
911
  if (!uuid) {
@@ -920,8 +917,8 @@ class ElectronBleTransport {
920
917
  if (forceCleanRunPromise && this.runPromise) {
921
918
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
922
919
  this.runPromise.reject(error);
923
- this.rejectAllProtocolV2Frames(error);
924
920
  this.runPromise = null;
921
+ this.runPromiseDeviceId = null;
925
922
  }
926
923
  try {
927
924
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -933,7 +930,7 @@ class ElectronBleTransport {
933
930
  }
934
931
  const protocolHint = expectedProtocol
935
932
  ? undefined
936
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
933
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
937
934
  if (protocolHint) {
938
935
  this.deviceProtocolHints.set(uuid, protocolHint);
939
936
  }
@@ -944,16 +941,31 @@ class ElectronBleTransport {
944
941
  catch (error) {
945
942
  this.handleBluetoothError(error);
946
943
  }
944
+ const mtuCleanup = this.createMtuSubscription(uuid);
945
+ if (mtuCleanup) {
946
+ this.mtuCleanups.set(uuid, mtuCleanup);
947
+ }
947
948
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
948
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
949
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
949
950
  yield window.desktopApi.nobleBle.subscribe(uuid);
951
+ yield this.refreshBlePacketCapacity(uuid);
950
952
  const cleanup = this.createNotificationSubscription(uuid);
951
953
  this.notificationCleanups.set(uuid, cleanup);
954
+ const connectionToken = this.notificationTokens.get(uuid);
955
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
956
+ if (protocolType === 'V2') {
957
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
958
+ writeMode: 'withoutResponse',
959
+ negotiatedMtu: this.deviceMtus.get(uuid),
960
+ packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
961
+ });
962
+ }
952
963
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
953
964
  var _a;
954
- if (disconnectedDevice.id === uuid) {
965
+ if (disconnectedDevice.id === uuid &&
966
+ this.notificationTokens.get(uuid) === connectionToken) {
955
967
  this.cleanupDeviceState(uuid);
956
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
968
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
957
969
  name: disconnectedDevice.name,
958
970
  id: disconnectedDevice.id,
959
971
  connectId: disconnectedDevice.id,
@@ -961,35 +973,46 @@ class ElectronBleTransport {
961
973
  }
962
974
  });
963
975
  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
976
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
971
977
  }
972
978
  catch (error) {
973
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
979
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
974
980
  try {
975
- if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
981
+ if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
976
982
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
977
983
  yield window.desktopApi.nobleBle.disconnect(uuid);
978
984
  }
979
985
  }
980
986
  catch (cleanupError) {
981
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
987
+ (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
982
988
  }
983
989
  this.cleanupDeviceState(uuid);
984
990
  throw error;
985
991
  }
986
992
  });
987
993
  }
988
- release(id) {
989
- var _a, _b;
994
+ release(id, _onclose, keepSession) {
995
+ var _a;
990
996
  return __awaiter(this, void 0, void 0, function* () {
991
997
  try {
992
998
  yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
999
+ yield this.releaseLogical(id, keepSession);
1000
+ }
1001
+ catch (error) {
1002
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
1003
+ this.cleanupDeviceState(id);
1004
+ }
1005
+ });
1006
+ }
1007
+ disconnect(id) {
1008
+ return __awaiter(this, void 0, void 0, function* () {
1009
+ return this.releaseNative(id);
1010
+ });
1011
+ }
1012
+ releaseNative(id) {
1013
+ var _a, _b;
1014
+ return __awaiter(this, void 0, void 0, function* () {
1015
+ try {
993
1016
  if (this.connectedDevices.has(id)) {
994
1017
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
995
1018
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -1004,11 +1027,35 @@ class ElectronBleTransport {
1004
1027
  }
1005
1028
  });
1006
1029
  }
1030
+ releaseLogical(id, keepSession) {
1031
+ var _a, _b, _c, _d;
1032
+ return __awaiter(this, void 0, void 0, function* () {
1033
+ try {
1034
+ if (!this.connectedDevices.has(id))
1035
+ return;
1036
+ this.cleanupDeviceState(id);
1037
+ const release = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.release;
1038
+ if (!release) {
1039
+ if (!this.warnedMissingRelease) {
1040
+ this.warnedMissingRelease = true;
1041
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] desktopApi.nobleBle.release is missing from the preload bridge; ' +
1042
+ 'keep-alive idle countdown will never start — map NOBLE_BLE_RELEASE in the desktop preload');
1043
+ }
1044
+ return;
1045
+ }
1046
+ yield release(id, keepSession);
1047
+ }
1048
+ catch (error) {
1049
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
1050
+ this.cleanupDeviceState(id);
1051
+ }
1052
+ });
1053
+ }
1007
1054
  createProtocolMismatchError(expected) {
1008
1055
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1009
1056
  }
1010
1057
  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');
1058
+ 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
1059
  }
1013
1060
  clearProbeProtocol(uuid, protocol) {
1014
1061
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1027,9 +1074,12 @@ class ElectronBleTransport {
1027
1074
  throw this.createProtocolMismatchError(expectedProtocol);
1028
1075
  }
1029
1076
  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';
1077
+ if (yield this.probeProtocolV2(uuid)) {
1078
+ this.deviceProtocol.set(uuid, 'V2');
1079
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1080
+ return 'V2';
1081
+ }
1082
+ throw this.createProtocolMismatchError(expectedProtocol);
1033
1083
  }
1034
1084
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1035
1085
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1063,16 +1113,17 @@ class ElectronBleTransport {
1063
1113
  });
1064
1114
  }
1065
1115
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1066
- var _a, _b, _c, _d, _e, _f, _g;
1116
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1067
1117
  return __awaiter(this, void 0, void 0, function* () {
1068
1118
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1069
1119
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1070
1120
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1071
1121
  this.resetProtocolV2Frames(uuid);
1072
- if (this.runPromise) {
1122
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1073
1123
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1074
1124
  this.runPromise.reject(error);
1075
1125
  this.runPromise = null;
1126
+ this.runPromiseDeviceId = null;
1076
1127
  }
1077
1128
  const notifyCleanup = this.notificationCleanups.get(uuid);
1078
1129
  if (notifyCleanup) {
@@ -1087,10 +1138,20 @@ class ElectronBleTransport {
1087
1138
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1088
1139
  }
1089
1140
  try {
1090
- yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
1141
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
1142
+ }
1143
+ catch (error) {
1144
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
1145
+ }
1146
+ this.connectedDevices.delete(uuid);
1147
+ try {
1148
+ yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
1149
+ this.connectedDevices.add(uuid);
1150
+ yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
1151
+ yield this.refreshBlePacketCapacity(uuid);
1091
1152
  }
1092
1153
  catch (error) {
1093
- (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1154
+ (_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
1094
1155
  throw error;
1095
1156
  }
1096
1157
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1105,12 +1166,12 @@ class ElectronBleTransport {
1105
1166
  }
1106
1167
  try {
1107
1168
  this.deviceProtocol.set(uuid, 'V1');
1108
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1169
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1109
1170
  return true;
1110
1171
  }
1111
1172
  catch (error) {
1112
1173
  this.clearProbeProtocol(uuid, 'V1');
1113
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1174
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1114
1175
  return false;
1115
1176
  }
1116
1177
  });
@@ -1140,25 +1201,6 @@ class ElectronBleTransport {
1140
1201
  return detected;
1141
1202
  });
1142
1203
  }
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
1204
  writeOnce(uuid, hexData) {
1163
1205
  var _a;
1164
1206
  return __awaiter(this, void 0, void 0, function* () {
@@ -1166,7 +1208,51 @@ class ElectronBleTransport {
1166
1208
  if (!nobleBle) {
1167
1209
  throw new Error('Noble BLE API not available');
1168
1210
  }
1169
- yield nobleBle.write(uuid, hexData);
1211
+ yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1212
+ });
1213
+ }
1214
+ refreshBlePacketCapacity(uuid) {
1215
+ var _a, _b;
1216
+ return __awaiter(this, void 0, void 0, function* () {
1217
+ const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1218
+ this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1219
+ });
1220
+ }
1221
+ updateBlePacketCapacity(uuid, mtu) {
1222
+ const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
1223
+ if (typeof mtu === 'number') {
1224
+ this.deviceMtus.set(uuid, mtu);
1225
+ }
1226
+ else {
1227
+ this.deviceMtus.delete(uuid);
1228
+ }
1229
+ this.devicePacketCapacities.set(uuid, packetCapacity);
1230
+ }
1231
+ createMtuSubscription(uuid) {
1232
+ var _a, _b;
1233
+ const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
1234
+ if (!onMtuChanged)
1235
+ return undefined;
1236
+ return onMtuChanged(device => {
1237
+ if (device.id === uuid) {
1238
+ this.updateBlePacketCapacity(uuid, device.mtu);
1239
+ }
1240
+ });
1241
+ }
1242
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
1243
+ var _a;
1244
+ const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
1245
+ const shouldPace = !context.highThroughput;
1246
+ return transport.writeProtocolV2BleFrame({
1247
+ frame,
1248
+ packetCapacity,
1249
+ assertActive: assertCurrentGeneration,
1250
+ signal: context.signal,
1251
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1252
+ initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
1253
+ burstSize: shouldPace ? 1 : undefined,
1254
+ burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
1255
+ writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
1170
1256
  });
1171
1257
  }
1172
1258
  handleNotification(deviceId, hexData) {
@@ -1177,7 +1263,7 @@ class ElectronBleTransport {
1177
1263
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1178
1264
  this.rejectProtocolV2Frames(deviceId, error);
1179
1265
  }
1180
- else if (this.runPromise) {
1266
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1181
1267
  this.runPromise.reject(error);
1182
1268
  }
1183
1269
  return;
@@ -1229,16 +1315,8 @@ class ElectronBleTransport {
1229
1315
  }
1230
1316
  this.getProtocolV2FrameQueue(uuid).push(frame);
1231
1317
  }
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
1318
  resetProtocolV2Frames(uuid) {
1240
- this.v2FrameQueues.delete(uuid);
1241
- this.v2FramePromises.delete(uuid);
1319
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1242
1320
  }
1243
1321
  rejectProtocolV2Frames(uuid, error) {
1244
1322
  this.v2FrameQueues.delete(uuid);
@@ -1271,17 +1349,20 @@ class ElectronBleTransport {
1271
1349
  const result = this.processProtocolV1Notification(deviceId, hexData);
1272
1350
  if (result.error) {
1273
1351
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1274
- if (this.runPromise) {
1352
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1275
1353
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1276
1354
  }
1277
1355
  return;
1278
1356
  }
1279
- if (result.isComplete && result.completePacket && this.runPromise) {
1357
+ if (result.isComplete &&
1358
+ result.completePacket &&
1359
+ this.runPromise &&
1360
+ this.runPromiseDeviceId === deviceId) {
1280
1361
  this.runPromise.resolve(result.completePacket);
1281
1362
  }
1282
1363
  }
1283
1364
  call(uuid, name, data, options) {
1284
- var _a, _b;
1365
+ var _a;
1285
1366
  return __awaiter(this, void 0, void 0, function* () {
1286
1367
  if (!this._messages) {
1287
1368
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1293,12 +1374,8 @@ class ElectronBleTransport {
1293
1374
  if (!protocol) {
1294
1375
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1295
1376
  }
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);
1377
+ if (!transport.shouldSuppressHighVolumeCallLog(name)) {
1378
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', transport.createTransportCallLog(name, protocol, data));
1302
1379
  }
1303
1380
  if (protocol === 'V2') {
1304
1381
  return this.callProtocolV2(uuid, name, data, options);
@@ -1319,6 +1396,7 @@ class ElectronBleTransport {
1319
1396
  const runPromise = hdShared.createDeferred();
1320
1397
  runPromise.promise.catch(() => undefined);
1321
1398
  this.runPromise = runPromise;
1399
+ this.runPromiseDeviceId = uuid;
1322
1400
  const messages = this._messages;
1323
1401
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1324
1402
  let timeout;
@@ -1357,6 +1435,17 @@ class ElectronBleTransport {
1357
1435
  }
1358
1436
  catch (e) {
1359
1437
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1438
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1439
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1440
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1441
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1442
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1443
+ this.notificationCleanups.delete(uuid);
1444
+ this.notificationTokens.delete(uuid);
1445
+ if (!isProbeTimeout) {
1446
+ yield this.releaseNative(uuid);
1447
+ }
1448
+ }
1360
1449
  throw e;
1361
1450
  }
1362
1451
  finally {
@@ -1364,22 +1453,23 @@ class ElectronBleTransport {
1364
1453
  clearTimeout(timeout);
1365
1454
  if (this.runPromise === runPromise) {
1366
1455
  this.runPromise = null;
1456
+ this.runPromiseDeviceId = null;
1367
1457
  }
1368
1458
  }
1369
1459
  });
1370
1460
  }
1371
1461
  callProtocolV2(uuid, name, data, options) {
1372
- var _a, _b;
1462
+ var _a;
1373
1463
  return __awaiter(this, void 0, void 0, function* () {
1374
1464
  if (!this._messages || !this._messagesV2) {
1375
1465
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1376
1466
  }
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 });
1467
+ const callOptions = options;
1378
1468
  try {
1379
1469
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1380
1470
  }
1381
1471
  catch (e) {
1382
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1472
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1383
1473
  throw e;
1384
1474
  }
1385
1475
  });
@@ -1394,6 +1484,7 @@ class ElectronBleTransport {
1394
1484
  };
1395
1485
  return {
1396
1486
  router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1487
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1397
1488
  generation,
1398
1489
  prepareCall: () => {
1399
1490
  var _a;
@@ -1401,10 +1492,7 @@ class ElectronBleTransport {
1401
1492
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1402
1493
  this.resetProtocolV2Frames(uuid);
1403
1494
  },
1404
- writeFrame: (frame) => {
1405
- assertCurrentGeneration();
1406
- return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1407
- },
1495
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1408
1496
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1409
1497
  assertCurrentGeneration();
1410
1498
  const rxFrame = yield this.readProtocolV2Frame(uuid);
@@ -1424,6 +1512,7 @@ class ElectronBleTransport {
1424
1512
  };
1425
1513
  }
1426
1514
  processProtocolV1Notification(deviceId, hexData) {
1515
+ var _a;
1427
1516
  try {
1428
1517
  if (typeof hexData !== 'string') {
1429
1518
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1442,6 +1531,10 @@ class ElectronBleTransport {
1442
1531
  bufferState.buffer = [...data.subarray(3)];
1443
1532
  }
1444
1533
  else {
1534
+ if (bufferState.buffer.length === 0) {
1535
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Orphan continuation chunk discarded');
1536
+ return { isComplete: false };
1537
+ }
1445
1538
  bufferState.buffer = bufferState.buffer.concat([...data]);
1446
1539
  }
1447
1540
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {