@onekeyfe/hd-transport-web-device 1.2.0-alpha.1 → 1.2.0-alpha.100

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
  }
@@ -420,19 +421,11 @@ class WebUsbTransport {
420
421
  return copied.buffer;
421
422
  }
422
423
  transferOutWithRetry(path, packet) {
423
- var _a;
424
424
  return __awaiter(this, void 0, void 0, function* () {
425
425
  let lastError;
426
426
  for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
427
427
  try {
428
- const device = yield this.findDevice(path);
429
- if (!device.opened) {
430
- yield this.connect(path, false);
431
- }
432
- const endpoints = this.deviceEndpoints.get(path);
433
- const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
434
- const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
435
- yield device.transferOut(endpointOut, transferBuffer);
428
+ yield this.transferOutOnce(path, packet);
436
429
  return;
437
430
  }
438
431
  catch (error) {
@@ -453,6 +446,19 @@ class WebUsbTransport {
453
446
  throw lastError;
454
447
  });
455
448
  }
449
+ transferOutOnce(path, packet) {
450
+ var _a;
451
+ return __awaiter(this, void 0, void 0, function* () {
452
+ const device = yield this.findDevice(path);
453
+ if (!device.opened) {
454
+ yield this.connect(path, false);
455
+ }
456
+ const endpoints = this.deviceEndpoints.get(path);
457
+ const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
458
+ const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
459
+ yield device.transferOut(endpointOut, transferBuffer);
460
+ });
461
+ }
456
462
  transferInWithRetry(path, length, cancelToken) {
457
463
  var _a;
458
464
  return __awaiter(this, void 0, void 0, function* () {
@@ -492,29 +498,28 @@ class WebUsbTransport {
492
498
  throw lastError;
493
499
  });
494
500
  }
495
- resetConnectionAfterProbe(path) {
496
- var _a, _b;
501
+ transferInOnce(path, length) {
502
+ var _a;
497
503
  return __awaiter(this, void 0, void 0, function* () {
498
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
499
- this.protocolV2Sessions.delete(path);
500
- this.protocolV2ReadTimeouts.delete(path);
501
- try {
502
- const device = yield this.findDevice(path);
503
- if (device.opened) {
504
- const endpoints = this.deviceEndpoints.get(path);
505
- const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
506
- try {
507
- yield device.releaseInterface(ifaceNum);
508
- }
509
- catch (error) {
510
- this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
511
- }
512
- yield device.close();
513
- }
514
- }
515
- catch (error) {
516
- 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');
517
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);
518
523
  yield this.getConnectedDevices();
519
524
  yield this.connect(path, false);
520
525
  });
@@ -537,6 +542,14 @@ class WebUsbTransport {
537
542
  timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
538
543
  timedOut = true;
539
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
+ }
540
553
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
541
554
  }), timeoutMs);
542
555
  }),
@@ -554,11 +567,12 @@ class WebUsbTransport {
554
567
  return false;
555
568
  }
556
569
  try {
557
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
570
+ yield this.callProtocolV1(path, 'Initialize', {}, {
571
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
572
+ });
558
573
  return true;
559
574
  }
560
- catch (error) {
561
- this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
575
+ catch (_error) {
562
576
  return false;
563
577
  }
564
578
  });
@@ -570,10 +584,9 @@ class WebUsbTransport {
570
584
  }
571
585
  return transport.probeProtocolV2({
572
586
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
573
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
587
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
574
588
  logger: this.Log,
575
589
  logPrefix: 'ProtocolV2 WebUSB',
576
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
577
590
  });
578
591
  });
579
592
  }
@@ -590,12 +603,8 @@ class WebUsbTransport {
590
603
  if (!protocol) {
591
604
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${path}`);
592
605
  }
593
- if (shouldSuppressWebUsbCallLog(name)) ;
594
- else if (shouldBlockWebUsbCallDataLog(name)) {
595
- this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
596
- }
597
- else {
598
- 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));
599
608
  }
600
609
  if (protocol === 'V2') {
601
610
  return this.callProtocolV2(path, name, data, options);
@@ -625,67 +634,8 @@ class WebUsbTransport {
625
634
  });
626
635
  }
627
636
  callProtocolV2(path, name, data, options) {
628
- var _a;
629
- return __awaiter(this, void 0, void 0, function* () {
630
- const protocolV1Messages = this.messages;
631
- if (!this.messagesV2) {
632
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured, 'Protocol V2 schema not configured');
633
- }
634
- if (!protocolV1Messages) {
635
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
636
- }
637
- let session = this.protocolV2Sessions.get(path);
638
- if (!session) {
639
- session = new transport.ProtocolV2Session({
640
- schemas: {
641
- protocolV1: protocolV1Messages,
642
- protocolV2: this.messagesV2,
643
- },
644
- router: transport.PROTOCOL_V2_CHANNEL_USB,
645
- writeFrame: (frame) => this.transferOutWithRetry(path, frame),
646
- readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
647
- logger: this.Log,
648
- logPrefix: 'ProtocolV2 WebUSB',
649
- createTimeoutError: (messageName, timeoutMs) => new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
650
- });
651
- this.protocolV2Sessions.set(path, session);
652
- }
653
- this.protocolV2ReadTimeouts.set(path, options === null || options === void 0 ? void 0 : options.timeoutMs);
654
- (_a = this.protocolV2Assemblers.get(path)) === null || _a === void 0 ? void 0 : _a.reset();
655
- try {
656
- return yield session.call(name, data, options);
657
- }
658
- finally {
659
- this.protocolV2ReadTimeouts.delete(path);
660
- }
661
- });
662
- }
663
- receiveProtocolV2Frame(path, timeoutMs) {
664
637
  return __awaiter(this, void 0, void 0, function* () {
665
- let assembler = this.protocolV2Assemblers.get(path);
666
- if (!assembler) {
667
- assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_FRAME_MAX_BYTES);
668
- this.protocolV2Assemblers.set(path, assembler);
669
- }
670
- let frame = assembler.push(new Uint8Array(0));
671
- const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
672
- while (!frame) {
673
- const cancelToken = { cancelled: false };
674
- const transferIn = this.transferInWithRetry(path, transport.PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
675
- const dataView = deadline
676
- ? yield this.withProtocolReadTimeout(path, transferIn, Math.max(deadline - Date.now(), 1), 'V2', () => {
677
- cancelToken.cancelled = true;
678
- })
679
- : yield transferIn;
680
- const bytes = new Uint8Array(this.toArrayBuffer(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)));
681
- try {
682
- frame = assembler.push(bytes);
683
- }
684
- catch (error) {
685
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.NetworkError, error instanceof Error ? error.message : String(error));
686
- }
687
- }
688
- return frame;
638
+ return this.callProtocolV2Usb(path, name, data, options);
689
639
  });
690
640
  }
691
641
  receiveData(path, timeoutMs) {
@@ -726,44 +676,71 @@ class WebUsbTransport {
726
676
  });
727
677
  }
728
678
  release(path) {
729
- var _a, _b;
730
679
  return __awaiter(this, void 0, void 0, function* () {
731
- const device = yield this.findDevice(path);
732
- const endpoints = this.deviceEndpoints.get(path);
733
- const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
734
- yield device.releaseInterface(ifaceNum);
735
- yield device.close();
680
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
681
+ yield this.closeOpenDevice(path);
736
682
  this.deviceProtocol.delete(path);
737
683
  this.deviceProtocolHints.delete(path);
738
- (_b = this.protocolV2Assemblers.get(path)) === null || _b === void 0 ? void 0 : _b.reset();
739
- this.protocolV2Assemblers.delete(path);
740
684
  this.deviceEndpoints.delete(path);
741
685
  });
742
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
+ }
743
723
  getProtocolType(path) {
744
724
  return this.deviceProtocol.get(path);
745
725
  }
746
726
  }
747
727
 
748
- const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
749
- function shouldSuppressHighVolumeCallLog(name) {
750
- const normalized = name.replace(/[_\s-]/g, '');
751
- return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
752
- }
753
- function isLogBlockCommand(name) {
754
- var _a, _b;
755
- 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);
756
734
  }
735
+
757
736
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
758
737
  function inferProtocolHintFromDeviceName(name) {
759
738
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
760
739
  }
761
740
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
762
- const BLE_PACKET_SIZE = 192;
741
+ const BLE_PACKET_SIZE_FALLBACK = 192;
742
+ const BLE_PACKET_SIZE_MAXIMUM = 244;
763
743
  const BLE_WRITE_DELAY_MS = 5;
764
- const BLE_WRITE_MAX_RETRIES = 3;
765
- const BLE_WRITE_RETRY_DELAY_MS = 300;
766
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
767
744
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
768
745
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
769
746
  class ElectronBleTransport {
@@ -771,16 +748,40 @@ class ElectronBleTransport {
771
748
  this.name = 'ElectronBleTransport';
772
749
  this.configured = false;
773
750
  this.runPromise = null;
751
+ this.runPromiseDeviceId = null;
774
752
  this.connectedDevices = new Set();
775
753
  this.deviceProtocol = new Map();
776
754
  this.deviceProtocolHints = new Map();
755
+ this.deviceMtus = new Map();
756
+ this.devicePacketCapacities = new Map();
777
757
  this.v1Buffers = new Map();
778
758
  this.v2Assemblers = new Map();
779
759
  this.v2FrameQueues = new Map();
780
760
  this.v2FramePromises = new Map();
781
- this.activeProtocolV2Call = null;
782
- this.nextProtocolV2CallToken = 1;
761
+ this.protocolV2Links = new transport.ProtocolV2LinkManager({
762
+ getSchemas: () => {
763
+ if (!this._messages || !this._messagesV2) {
764
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
765
+ }
766
+ return {
767
+ protocolV1: this._messages,
768
+ protocolV2: this._messagesV2,
769
+ };
770
+ },
771
+ classifyError: () => 'link-fatal',
772
+ onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
773
+ var _a, _b;
774
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
775
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
776
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
777
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
778
+ yield this.releaseNative(uuid);
779
+ }
780
+ }),
781
+ });
782
+ this.warnedMissingRelease = false;
783
783
  this.notificationCleanups = new Map();
784
+ this.mtuCleanups = new Map();
784
785
  this.disconnectCleanups = new Map();
785
786
  this.notificationTokens = new Map();
786
787
  this.nextNotificationToken = 1;
@@ -815,21 +816,32 @@ class ElectronBleTransport {
815
816
  throw error;
816
817
  }
817
818
  cleanupDeviceState(deviceId) {
818
- var _a;
819
+ this.protocolV2Links
820
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
821
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
819
822
  this.connectedDevices.delete(deviceId);
820
823
  this.deviceProtocol.delete(deviceId);
824
+ this.deviceMtus.delete(deviceId);
825
+ this.devicePacketCapacities.delete(deviceId);
821
826
  this.v1Buffers.delete(deviceId);
822
827
  this.v2Assemblers.delete(deviceId);
823
828
  this.resetProtocolV2Frames(deviceId);
824
- if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
825
- this.activeProtocolV2Call = null;
826
- }
827
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
+ }
828
835
  const notifyCleanup = this.notificationCleanups.get(deviceId);
829
836
  if (notifyCleanup) {
830
837
  notifyCleanup();
831
838
  this.notificationCleanups.delete(deviceId);
832
839
  }
840
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
841
+ if (mtuCleanup) {
842
+ mtuCleanup();
843
+ this.mtuCleanups.delete(deviceId);
844
+ }
833
845
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
834
846
  if (disconnectCleanup) {
835
847
  disconnectCleanup();
@@ -850,9 +862,18 @@ class ElectronBleTransport {
850
862
  this.configured = true;
851
863
  }
852
864
  configureProtocolV2(signedData) {
853
- 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;
854
870
  this._messagesV2 = parseConfigure(signedData);
855
- (_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
+ }
856
877
  }
857
878
  listen() {
858
879
  return __awaiter(this, void 0, void 0, function* () {
@@ -884,18 +905,20 @@ class ElectronBleTransport {
884
905
  });
885
906
  }
886
907
  acquire(input) {
887
- var _a, _b, _c, _d, _e, _f;
908
+ var _a, _b, _c, _d, _e, _f, _g, _h;
888
909
  return __awaiter(this, void 0, void 0, function* () {
889
910
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
890
911
  if (!uuid) {
891
912
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
892
913
  }
914
+ if (this.connectedDevices.has(uuid)) {
915
+ yield this.release(uuid);
916
+ }
893
917
  if (forceCleanRunPromise && this.runPromise) {
894
918
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
895
919
  this.runPromise.reject(error);
896
- this.rejectAllProtocolV2Frames(error);
897
920
  this.runPromise = null;
898
- this.activeProtocolV2Call = null;
921
+ this.runPromiseDeviceId = null;
899
922
  }
900
923
  try {
901
924
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -907,7 +930,7 @@ class ElectronBleTransport {
907
930
  }
908
931
  const protocolHint = expectedProtocol
909
932
  ? undefined
910
- : (_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);
911
934
  if (protocolHint) {
912
935
  this.deviceProtocolHints.set(uuid, protocolHint);
913
936
  }
@@ -918,16 +941,31 @@ class ElectronBleTransport {
918
941
  catch (error) {
919
942
  this.handleBluetoothError(error);
920
943
  }
944
+ const mtuCleanup = this.createMtuSubscription(uuid);
945
+ if (mtuCleanup) {
946
+ this.mtuCleanups.set(uuid, mtuCleanup);
947
+ }
921
948
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
922
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
949
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
923
950
  yield window.desktopApi.nobleBle.subscribe(uuid);
951
+ yield this.refreshBlePacketCapacity(uuid);
924
952
  const cleanup = this.createNotificationSubscription(uuid);
925
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
+ }
926
963
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
927
964
  var _a;
928
- if (disconnectedDevice.id === uuid) {
965
+ if (disconnectedDevice.id === uuid &&
966
+ this.notificationTokens.get(uuid) === connectionToken) {
929
967
  this.cleanupDeviceState(uuid);
930
- (_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, {
931
969
  name: disconnectedDevice.name,
932
970
  id: disconnectedDevice.id,
933
971
  connectId: disconnectedDevice.id,
@@ -935,31 +973,43 @@ class ElectronBleTransport {
935
973
  }
936
974
  });
937
975
  this.disconnectCleanups.set(uuid, disconnectCleanup);
938
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
939
- (_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
940
- name: device.name,
941
- id: device.id,
942
- connectId: device.id,
943
- });
944
976
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
945
977
  }
946
978
  catch (error) {
947
- (_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);
948
980
  try {
949
- 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)) {
950
982
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
951
983
  yield window.desktopApi.nobleBle.disconnect(uuid);
952
984
  }
953
985
  }
954
986
  catch (cleanupError) {
955
- (_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);
956
988
  }
957
989
  this.cleanupDeviceState(uuid);
958
990
  throw error;
959
991
  }
960
992
  });
961
993
  }
962
- release(id) {
994
+ release(id, _onclose, keepSession) {
995
+ var _a;
996
+ return __awaiter(this, void 0, void 0, function* () {
997
+ try {
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) {
963
1013
  var _a, _b;
964
1014
  return __awaiter(this, void 0, void 0, function* () {
965
1015
  try {
@@ -977,11 +1027,35 @@ class ElectronBleTransport {
977
1027
  }
978
1028
  });
979
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
+ }
980
1054
  createProtocolMismatchError(expected) {
981
1055
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
982
1056
  }
983
1057
  createProtocolDetectionError() {
984
- 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');
985
1059
  }
986
1060
  clearProbeProtocol(uuid, protocol) {
987
1061
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1000,9 +1074,12 @@ class ElectronBleTransport {
1000
1074
  throw this.createProtocolMismatchError(expectedProtocol);
1001
1075
  }
1002
1076
  if (expectedProtocol === 'V2') {
1003
- this.deviceProtocol.set(uuid, 'V2');
1004
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1005
- 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);
1006
1083
  }
1007
1084
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1008
1085
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1036,18 +1113,17 @@ class ElectronBleTransport {
1036
1113
  });
1037
1114
  }
1038
1115
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1039
- var _a, _b, _c, _d, _e, _f, _g, _h;
1116
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1040
1117
  return __awaiter(this, void 0, void 0, function* () {
1118
+ yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1041
1119
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1042
1120
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1043
1121
  this.resetProtocolV2Frames(uuid);
1044
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
1045
- this.activeProtocolV2Call = null;
1046
- }
1047
- if (this.runPromise) {
1122
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1048
1123
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1049
1124
  this.runPromise.reject(error);
1050
1125
  this.runPromise = null;
1126
+ this.runPromiseDeviceId = null;
1051
1127
  }
1052
1128
  const notifyCleanup = this.notificationCleanups.get(uuid);
1053
1129
  if (notifyCleanup) {
@@ -1056,16 +1132,26 @@ class ElectronBleTransport {
1056
1132
  }
1057
1133
  this.notificationTokens.delete(uuid);
1058
1134
  try {
1059
- yield ((_d = (_c = window.desktopApi) === null || _c === void 0 ? void 0 : _c.nobleBle) === null || _d === void 0 ? void 0 : _d.unsubscribe(uuid));
1135
+ yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
1136
+ }
1137
+ catch (error) {
1138
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1139
+ }
1140
+ try {
1141
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
1060
1142
  }
1061
1143
  catch (error) {
1062
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1144
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
1063
1145
  }
1146
+ this.connectedDevices.delete(uuid);
1064
1147
  try {
1065
- yield ((_g = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) === null || _g === void 0 ? void 0 : _g.subscribe(uuid));
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);
1066
1152
  }
1067
1153
  catch (error) {
1068
- (_h = this.Log) === null || _h === void 0 ? void 0 : _h.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);
1069
1155
  throw error;
1070
1156
  }
1071
1157
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1080,12 +1166,12 @@ class ElectronBleTransport {
1080
1166
  }
1081
1167
  try {
1082
1168
  this.deviceProtocol.set(uuid, 'V1');
1083
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1169
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1084
1170
  return true;
1085
1171
  }
1086
1172
  catch (error) {
1087
1173
  this.clearProbeProtocol(uuid, 'V1');
1088
- (_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);
1089
1175
  return false;
1090
1176
  }
1091
1177
  });
@@ -1115,57 +1201,70 @@ class ElectronBleTransport {
1115
1201
  return detected;
1116
1202
  });
1117
1203
  }
1118
- writeWithChunking(uuid, hexData) {
1119
- return __awaiter(this, void 0, void 0, function* () {
1120
- const totalBytes = hexData.length / 2;
1121
- if (totalBytes <= BLE_PACKET_SIZE) {
1122
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1123
- yield this.writeWithRetry(uuid, hexData);
1124
- return;
1125
- }
1126
- for (let offset = 0; offset < hexData.length;) {
1127
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1128
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1129
- offset += chunkHexLen;
1130
- yield this.writeWithRetry(uuid, chunkHex);
1131
- if (offset < hexData.length) {
1132
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1133
- }
1134
- }
1135
- });
1136
- }
1137
- writeWithRetry(uuid, hexData) {
1138
- var _a, _b, _c;
1204
+ writeOnce(uuid, hexData) {
1205
+ var _a;
1139
1206
  return __awaiter(this, void 0, void 0, function* () {
1140
- let lastError;
1141
1207
  const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
1142
1208
  if (!nobleBle) {
1143
1209
  throw new Error('Noble BLE API not available');
1144
1210
  }
1145
- for (let attempt = 1; attempt <= BLE_WRITE_MAX_RETRIES; attempt++) {
1146
- try {
1147
- yield nobleBle.write(uuid, hexData);
1148
- return;
1149
- }
1150
- catch (error) {
1151
- lastError = error;
1152
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error(`[Electron BLE] write failed (attempt ${attempt}/${BLE_WRITE_MAX_RETRIES}):`, error);
1153
- if (attempt < BLE_WRITE_MAX_RETRIES) {
1154
- yield hdShared.wait(BLE_WRITE_RETRY_DELAY_MS);
1155
- }
1156
- }
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);
1157
1239
  }
1158
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write failed after ${BLE_WRITE_MAX_RETRIES} attempts: ${(_c = lastError === null || lastError === void 0 ? void 0 : lastError.message) !== null && _c !== void 0 ? _c : lastError}`);
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)),
1159
1256
  });
1160
1257
  }
1161
1258
  handleNotification(deviceId, hexData) {
1162
1259
  var _a, _b;
1163
1260
  if (hexData === 'PAIRING_REJECTED') {
1164
1261
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
1165
- if (this.runPromise) {
1166
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1262
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1263
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
1264
+ this.rejectProtocolV2Frames(deviceId, error);
1265
+ }
1266
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1167
1267
  this.runPromise.reject(error);
1168
- this.rejectAllProtocolV2Frames(error);
1169
1268
  }
1170
1269
  return;
1171
1270
  }
@@ -1181,13 +1280,8 @@ class ElectronBleTransport {
1181
1280
  this.handleProtocolV1Notification(deviceId, hexData);
1182
1281
  }
1183
1282
  handleProtocolV2Notification(deviceId, hexData) {
1184
- var _a, _b, _c;
1283
+ var _a;
1185
1284
  try {
1186
- if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== deviceId) {
1187
- (_b = this.v2Assemblers.get(deviceId)) === null || _b === void 0 ? void 0 : _b.reset();
1188
- this.resetProtocolV2Frames(deviceId);
1189
- return;
1190
- }
1191
1285
  const bytes = transport.hexToBytes(hexData);
1192
1286
  if (bytes.length === 0)
1193
1287
  return;
@@ -1199,12 +1293,9 @@ class ElectronBleTransport {
1199
1293
  }
1200
1294
  }
1201
1295
  catch (error) {
1202
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] Protocol V2 notification error:', error);
1203
- if (this.runPromise) {
1204
- const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1205
- this.runPromise.reject(notifyError);
1206
- this.rejectAllProtocolV2Frames(notifyError);
1207
- }
1296
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 notification error:', error);
1297
+ const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1298
+ this.rejectProtocolV2Frames(deviceId, notifyError);
1208
1299
  }
1209
1300
  }
1210
1301
  getProtocolV2FrameQueue(uuid) {
@@ -1224,20 +1315,16 @@ class ElectronBleTransport {
1224
1315
  }
1225
1316
  this.getProtocolV2FrameQueue(uuid).push(frame);
1226
1317
  }
1227
- rejectAllProtocolV2Frames(error) {
1228
- this.v2FrameQueues.clear();
1229
- for (const framePromise of this.v2FramePromises.values()) {
1230
- framePromise.reject(error);
1231
- }
1232
- this.v2FramePromises.clear();
1233
- }
1234
1318
  resetProtocolV2Frames(uuid) {
1235
- this.v2FrameQueues.delete(uuid);
1236
- this.v2FramePromises.delete(uuid);
1319
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1237
1320
  }
1238
- isActiveProtocolV2Call(uuid, token) {
1239
- var _a;
1240
- return ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === uuid && this.activeProtocolV2Call.token === token;
1321
+ rejectProtocolV2Frames(uuid, error) {
1322
+ this.v2FrameQueues.delete(uuid);
1323
+ const framePromise = this.v2FramePromises.get(uuid);
1324
+ if (framePromise) {
1325
+ this.v2FramePromises.delete(uuid);
1326
+ framePromise.reject(error);
1327
+ }
1241
1328
  }
1242
1329
  readProtocolV2Frame(uuid) {
1243
1330
  return __awaiter(this, void 0, void 0, function* () {
@@ -1262,17 +1349,20 @@ class ElectronBleTransport {
1262
1349
  const result = this.processProtocolV1Notification(deviceId, hexData);
1263
1350
  if (result.error) {
1264
1351
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1265
- if (this.runPromise) {
1352
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1266
1353
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1267
1354
  }
1268
1355
  return;
1269
1356
  }
1270
- if (result.isComplete && result.completePacket && this.runPromise) {
1357
+ if (result.isComplete &&
1358
+ result.completePacket &&
1359
+ this.runPromise &&
1360
+ this.runPromiseDeviceId === deviceId) {
1271
1361
  this.runPromise.resolve(result.completePacket);
1272
1362
  }
1273
1363
  }
1274
1364
  call(uuid, name, data, options) {
1275
- var _a, _b;
1365
+ var _a;
1276
1366
  return __awaiter(this, void 0, void 0, function* () {
1277
1367
  if (!this._messages) {
1278
1368
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1284,12 +1374,8 @@ class ElectronBleTransport {
1284
1374
  if (!protocol) {
1285
1375
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1286
1376
  }
1287
- if (shouldSuppressHighVolumeCallLog(name)) ;
1288
- else if (isLogBlockCommand(name)) {
1289
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
1290
- }
1291
- else {
1292
- (_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));
1293
1379
  }
1294
1380
  if (protocol === 'V2') {
1295
1381
  return this.callProtocolV2(uuid, name, data, options);
@@ -1310,6 +1396,7 @@ class ElectronBleTransport {
1310
1396
  const runPromise = hdShared.createDeferred();
1311
1397
  runPromise.promise.catch(() => undefined);
1312
1398
  this.runPromise = runPromise;
1399
+ this.runPromiseDeviceId = uuid;
1313
1400
  const messages = this._messages;
1314
1401
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1315
1402
  let timeout;
@@ -1348,6 +1435,17 @@ class ElectronBleTransport {
1348
1435
  }
1349
1436
  catch (e) {
1350
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
+ }
1351
1449
  throw e;
1352
1450
  }
1353
1451
  finally {
@@ -1355,82 +1453,66 @@ class ElectronBleTransport {
1355
1453
  clearTimeout(timeout);
1356
1454
  if (this.runPromise === runPromise) {
1357
1455
  this.runPromise = null;
1456
+ this.runPromiseDeviceId = null;
1358
1457
  }
1359
1458
  }
1360
1459
  });
1361
1460
  }
1362
1461
  callProtocolV2(uuid, name, data, options) {
1363
- var _a, _b, _c, _d, _e;
1462
+ var _a;
1364
1463
  return __awaiter(this, void 0, void 0, function* () {
1365
1464
  if (!this._messages || !this._messagesV2) {
1366
1465
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1367
1466
  }
1368
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
1369
- if (this.runPromise) {
1370
- if (!forceRun) {
1371
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1372
- }
1373
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1374
- this.runPromise.reject(error);
1375
- this.rejectAllProtocolV2Frames(error);
1376
- this.runPromise = null;
1377
- this.activeProtocolV2Call = null;
1378
- }
1379
- const runPromise = hdShared.createDeferred();
1380
- runPromise.promise.catch(() => undefined);
1381
- this.runPromise = runPromise;
1382
- const callToken = this.nextProtocolV2CallToken++;
1383
- this.activeProtocolV2Call = { uuid, token: callToken };
1384
- (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1385
- this.resetProtocolV2Frames(uuid);
1386
- let completed = false;
1387
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : BLE_RESPONSE_TIMEOUT_MS });
1467
+ const callOptions = options;
1388
1468
  try {
1389
- const session = new transport.ProtocolV2Session({
1390
- schemas: {
1391
- protocolV1: this._messages,
1392
- protocolV2: this._messagesV2,
1393
- },
1394
- router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1395
- writeFrame: (frame) => this.writeWithChunking(uuid, transport.bytesToHex(frame)),
1396
- readFrame: () => __awaiter(this, void 0, void 0, function* () {
1397
- const rxFrame = yield this.readProtocolV2Frame(uuid);
1398
- if (!(rxFrame instanceof Uint8Array)) {
1399
- throw new Error('Response is not Uint8Array');
1400
- }
1401
- return rxFrame;
1402
- }),
1403
- logger: this.Log,
1404
- logPrefix: 'ProtocolV2 BLE',
1405
- createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
1406
- });
1407
- const result = yield session.call(name, data, callOptions);
1408
- completed = true;
1409
- return result;
1469
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1410
1470
  }
1411
1471
  catch (e) {
1412
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1413
- (_c = this.v2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
1414
- this.resetProtocolV2Frames(uuid);
1415
- }
1416
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.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);
1417
1473
  throw e;
1418
1474
  }
1419
- finally {
1420
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1421
- if (!completed) {
1422
- (_e = this.v2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1423
- }
1424
- this.resetProtocolV2Frames(uuid);
1425
- this.activeProtocolV2Call = null;
1426
- }
1427
- if (this.runPromise === runPromise) {
1428
- this.runPromise = null;
1429
- }
1430
- }
1431
1475
  });
1432
1476
  }
1477
+ createProtocolV2Adapter(uuid) {
1478
+ var _a;
1479
+ const generation = (_a = this.notificationTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1480
+ const assertCurrentGeneration = () => {
1481
+ if (this.notificationTokens.get(uuid) !== generation) {
1482
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
1483
+ }
1484
+ };
1485
+ return {
1486
+ router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1487
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1488
+ generation,
1489
+ prepareCall: () => {
1490
+ var _a;
1491
+ assertCurrentGeneration();
1492
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1493
+ this.resetProtocolV2Frames(uuid);
1494
+ },
1495
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1496
+ readFrame: () => __awaiter(this, void 0, void 0, function* () {
1497
+ assertCurrentGeneration();
1498
+ const rxFrame = yield this.readProtocolV2Frame(uuid);
1499
+ if (!(rxFrame instanceof Uint8Array)) {
1500
+ throw new Error('Response is not Uint8Array');
1501
+ }
1502
+ return rxFrame;
1503
+ }),
1504
+ reset: (reason) => {
1505
+ var _a;
1506
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1507
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
1508
+ },
1509
+ logger: this.Log,
1510
+ logPrefix: 'ProtocolV2 BLE',
1511
+ createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
1512
+ };
1513
+ }
1433
1514
  processProtocolV1Notification(deviceId, hexData) {
1515
+ var _a;
1434
1516
  try {
1435
1517
  if (typeof hexData !== 'string') {
1436
1518
  return { isComplete: false, error: 'Invalid hexData type' };
@@ -1449,6 +1531,10 @@ class ElectronBleTransport {
1449
1531
  bufferState.buffer = [...data.subarray(3)];
1450
1532
  }
1451
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
+ }
1452
1538
  bufferState.buffer = bufferState.buffer.concat([...data]);
1453
1539
  }
1454
1540
  if (bufferState.buffer.length - transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferState.bufferLength) {