@onekeyfe/hd-transport-web-device 1.2.0-alpha.7 → 1.2.0-alpha.71

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,16 +748,39 @@ 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();
783
760
  this.v2FramePromises = new Map();
784
- this.activeProtocolV2Call = null;
785
- 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
+ });
786
782
  this.notificationCleanups = new Map();
783
+ this.mtuCleanups = new Map();
787
784
  this.disconnectCleanups = new Map();
788
785
  this.notificationTokens = new Map();
789
786
  this.nextNotificationToken = 1;
@@ -818,21 +815,32 @@ class ElectronBleTransport {
818
815
  throw error;
819
816
  }
820
817
  cleanupDeviceState(deviceId) {
821
- var _a;
818
+ this.protocolV2Links
819
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
820
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
822
821
  this.connectedDevices.delete(deviceId);
823
822
  this.deviceProtocol.delete(deviceId);
823
+ this.deviceMtus.delete(deviceId);
824
+ this.devicePacketCapacities.delete(deviceId);
824
825
  this.v1Buffers.delete(deviceId);
825
826
  this.v2Assemblers.delete(deviceId);
826
827
  this.resetProtocolV2Frames(deviceId);
827
- if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
828
- this.activeProtocolV2Call = null;
829
- }
830
828
  this.notificationTokens.delete(deviceId);
829
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
830
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
831
+ this.runPromise = null;
832
+ this.runPromiseDeviceId = null;
833
+ }
831
834
  const notifyCleanup = this.notificationCleanups.get(deviceId);
832
835
  if (notifyCleanup) {
833
836
  notifyCleanup();
834
837
  this.notificationCleanups.delete(deviceId);
835
838
  }
839
+ const mtuCleanup = this.mtuCleanups.get(deviceId);
840
+ if (mtuCleanup) {
841
+ mtuCleanup();
842
+ this.mtuCleanups.delete(deviceId);
843
+ }
836
844
  const disconnectCleanup = this.disconnectCleanups.get(deviceId);
837
845
  if (disconnectCleanup) {
838
846
  disconnectCleanup();
@@ -853,9 +861,18 @@ class ElectronBleTransport {
853
861
  this.configured = true;
854
862
  }
855
863
  configureProtocolV2(signedData) {
856
- var _a;
864
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
865
+ if (this.protocolV2SchemaConfiguration === configuration) {
866
+ return;
867
+ }
868
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
857
869
  this._messagesV2 = parseConfigure(signedData);
858
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V2 schema configured');
870
+ this.protocolV2SchemaConfiguration = configuration;
871
+ if (isReconfiguration) {
872
+ this.protocolV2Links
873
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
874
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
875
+ }
859
876
  }
860
877
  listen() {
861
878
  return __awaiter(this, void 0, void 0, function* () {
@@ -887,18 +904,20 @@ class ElectronBleTransport {
887
904
  });
888
905
  }
889
906
  acquire(input) {
890
- var _a, _b, _c, _d, _e, _f;
907
+ var _a, _b, _c, _d, _e, _f, _g, _h;
891
908
  return __awaiter(this, void 0, void 0, function* () {
892
909
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
893
910
  if (!uuid) {
894
911
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
895
912
  }
913
+ if (this.connectedDevices.has(uuid)) {
914
+ yield this.release(uuid);
915
+ }
896
916
  if (forceCleanRunPromise && this.runPromise) {
897
917
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
898
918
  this.runPromise.reject(error);
899
- this.rejectAllProtocolV2Frames(error);
900
919
  this.runPromise = null;
901
- this.activeProtocolV2Call = null;
920
+ this.runPromiseDeviceId = null;
902
921
  }
903
922
  try {
904
923
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -910,7 +929,7 @@ class ElectronBleTransport {
910
929
  }
911
930
  const protocolHint = expectedProtocol
912
931
  ? undefined
913
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
932
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
914
933
  if (protocolHint) {
915
934
  this.deviceProtocolHints.set(uuid, protocolHint);
916
935
  }
@@ -921,16 +940,31 @@ class ElectronBleTransport {
921
940
  catch (error) {
922
941
  this.handleBluetoothError(error);
923
942
  }
943
+ const mtuCleanup = this.createMtuSubscription(uuid);
944
+ if (mtuCleanup) {
945
+ this.mtuCleanups.set(uuid, mtuCleanup);
946
+ }
924
947
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
925
- this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
948
+ this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
926
949
  yield window.desktopApi.nobleBle.subscribe(uuid);
950
+ yield this.refreshBlePacketCapacity(uuid);
927
951
  const cleanup = this.createNotificationSubscription(uuid);
928
952
  this.notificationCleanups.set(uuid, cleanup);
953
+ const connectionToken = this.notificationTokens.get(uuid);
954
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
955
+ if (protocolType === 'V2') {
956
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[Electron BLE] Protocol V2 write configured', {
957
+ writeMode: 'withoutResponse',
958
+ negotiatedMtu: this.deviceMtus.get(uuid),
959
+ packetCapacity: (_e = this.devicePacketCapacities.get(uuid)) !== null && _e !== void 0 ? _e : BLE_PACKET_SIZE_FALLBACK,
960
+ });
961
+ }
929
962
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
930
963
  var _a;
931
- if (disconnectedDevice.id === uuid) {
964
+ if (disconnectedDevice.id === uuid &&
965
+ this.notificationTokens.get(uuid) === connectionToken) {
932
966
  this.cleanupDeviceState(uuid);
933
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
967
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
934
968
  name: disconnectedDevice.name,
935
969
  id: disconnectedDevice.id,
936
970
  connectId: disconnectedDevice.id,
@@ -938,24 +972,18 @@ class ElectronBleTransport {
938
972
  }
939
973
  });
940
974
  this.disconnectCleanups.set(uuid, disconnectCleanup);
941
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
942
- (_c = this.emitter) === null || _c === void 0 ? void 0 : _c.emit('device-connect', {
943
- name: device.name,
944
- id: device.id,
945
- connectId: device.id,
946
- });
947
975
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
948
976
  }
949
977
  catch (error) {
950
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
978
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.error('[Electron BLE] acquire failed:', error);
951
979
  try {
952
- if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
980
+ if (((_g = window.desktopApi) === null || _g === void 0 ? void 0 : _g.nobleBle) && this.connectedDevices.has(uuid)) {
953
981
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
954
982
  yield window.desktopApi.nobleBle.disconnect(uuid);
955
983
  }
956
984
  }
957
985
  catch (cleanupError) {
958
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
986
+ (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
959
987
  }
960
988
  this.cleanupDeviceState(uuid);
961
989
  throw error;
@@ -963,6 +991,19 @@ class ElectronBleTransport {
963
991
  });
964
992
  }
965
993
  release(id) {
994
+ var _a;
995
+ return __awaiter(this, void 0, void 0, function* () {
996
+ try {
997
+ yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
998
+ yield this.releaseNative(id);
999
+ }
1000
+ catch (error) {
1001
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] release failed:', error);
1002
+ this.cleanupDeviceState(id);
1003
+ }
1004
+ });
1005
+ }
1006
+ releaseNative(id) {
966
1007
  var _a, _b;
967
1008
  return __awaiter(this, void 0, void 0, function* () {
968
1009
  try {
@@ -984,7 +1025,7 @@ class ElectronBleTransport {
984
1025
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
985
1026
  }
986
1027
  createProtocolDetectionError() {
987
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1028
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
988
1029
  }
989
1030
  clearProbeProtocol(uuid, protocol) {
990
1031
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1003,9 +1044,12 @@ class ElectronBleTransport {
1003
1044
  throw this.createProtocolMismatchError(expectedProtocol);
1004
1045
  }
1005
1046
  if (expectedProtocol === 'V2') {
1006
- this.deviceProtocol.set(uuid, 'V2');
1007
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1008
- return 'V2';
1047
+ if (yield this.probeProtocolV2(uuid)) {
1048
+ this.deviceProtocol.set(uuid, 'V2');
1049
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1050
+ return 'V2';
1051
+ }
1052
+ throw this.createProtocolMismatchError(expectedProtocol);
1009
1053
  }
1010
1054
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1011
1055
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1039,18 +1083,17 @@ class ElectronBleTransport {
1039
1083
  });
1040
1084
  }
1041
1085
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1042
- var _a, _b, _c, _d, _e, _f, _g, _h;
1086
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1043
1087
  return __awaiter(this, void 0, void 0, function* () {
1088
+ yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1044
1089
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1045
1090
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1046
1091
  this.resetProtocolV2Frames(uuid);
1047
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
1048
- this.activeProtocolV2Call = null;
1049
- }
1050
- if (this.runPromise) {
1092
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1051
1093
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1052
1094
  this.runPromise.reject(error);
1053
1095
  this.runPromise = null;
1096
+ this.runPromiseDeviceId = null;
1054
1097
  }
1055
1098
  const notifyCleanup = this.notificationCleanups.get(uuid);
1056
1099
  if (notifyCleanup) {
@@ -1059,16 +1102,26 @@ class ElectronBleTransport {
1059
1102
  }
1060
1103
  this.notificationTokens.delete(uuid);
1061
1104
  try {
1062
- yield ((_d = (_c = window.desktopApi) === null || _c === void 0 ? void 0 : _c.nobleBle) === null || _d === void 0 ? void 0 : _d.unsubscribe(uuid));
1105
+ yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
1063
1106
  }
1064
1107
  catch (error) {
1065
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1108
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1066
1109
  }
1067
1110
  try {
1068
- yield ((_g = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) === null || _g === void 0 ? void 0 : _g.subscribe(uuid));
1111
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
1069
1112
  }
1070
1113
  catch (error) {
1071
- (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1114
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
1115
+ }
1116
+ this.connectedDevices.delete(uuid);
1117
+ try {
1118
+ yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
1119
+ this.connectedDevices.add(uuid);
1120
+ yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
1121
+ yield this.refreshBlePacketCapacity(uuid);
1122
+ }
1123
+ catch (error) {
1124
+ (_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
1072
1125
  throw error;
1073
1126
  }
1074
1127
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1083,12 +1136,12 @@ class ElectronBleTransport {
1083
1136
  }
1084
1137
  try {
1085
1138
  this.deviceProtocol.set(uuid, 'V1');
1086
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1139
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1087
1140
  return true;
1088
1141
  }
1089
1142
  catch (error) {
1090
1143
  this.clearProbeProtocol(uuid, 'V1');
1091
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1144
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1092
1145
  return false;
1093
1146
  }
1094
1147
  });
@@ -1118,25 +1171,6 @@ class ElectronBleTransport {
1118
1171
  return detected;
1119
1172
  });
1120
1173
  }
1121
- writeWithChunking(uuid, hexData) {
1122
- return __awaiter(this, void 0, void 0, function* () {
1123
- const totalBytes = hexData.length / 2;
1124
- if (totalBytes <= BLE_PACKET_SIZE) {
1125
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1126
- yield this.writeOnce(uuid, hexData);
1127
- return;
1128
- }
1129
- for (let offset = 0; offset < hexData.length;) {
1130
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1131
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1132
- offset += chunkHexLen;
1133
- yield this.writeOnce(uuid, chunkHex);
1134
- if (offset < hexData.length) {
1135
- yield hdShared.wait(BLE_WRITE_DELAY_MS);
1136
- }
1137
- }
1138
- });
1139
- }
1140
1174
  writeOnce(uuid, hexData) {
1141
1175
  var _a;
1142
1176
  return __awaiter(this, void 0, void 0, function* () {
@@ -1147,14 +1181,60 @@ class ElectronBleTransport {
1147
1181
  yield nobleBle.write(uuid, hexData);
1148
1182
  });
1149
1183
  }
1184
+ refreshBlePacketCapacity(uuid) {
1185
+ var _a, _b;
1186
+ return __awaiter(this, void 0, void 0, function* () {
1187
+ const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1188
+ this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1189
+ });
1190
+ }
1191
+ updateBlePacketCapacity(uuid, mtu) {
1192
+ const packetCapacity = resolveBlePacketCapacity(mtu, BLE_PACKET_SIZE_MAXIMUM, BLE_PACKET_SIZE_FALLBACK);
1193
+ if (typeof mtu === 'number') {
1194
+ this.deviceMtus.set(uuid, mtu);
1195
+ }
1196
+ else {
1197
+ this.deviceMtus.delete(uuid);
1198
+ }
1199
+ this.devicePacketCapacities.set(uuid, packetCapacity);
1200
+ }
1201
+ createMtuSubscription(uuid) {
1202
+ var _a, _b;
1203
+ const onMtuChanged = (_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.onMtuChanged;
1204
+ if (!onMtuChanged)
1205
+ return undefined;
1206
+ return onMtuChanged(device => {
1207
+ if (device.id === uuid) {
1208
+ this.updateBlePacketCapacity(uuid, device.mtu);
1209
+ }
1210
+ });
1211
+ }
1212
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
1213
+ var _a;
1214
+ const packetCapacity = (_a = this.devicePacketCapacities.get(uuid)) !== null && _a !== void 0 ? _a : BLE_PACKET_SIZE_FALLBACK;
1215
+ const shouldPace = !context.highVolume;
1216
+ return transport.writeProtocolV2BleFrame({
1217
+ frame,
1218
+ packetCapacity,
1219
+ assertActive: assertCurrentGeneration,
1220
+ signal: context.signal,
1221
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1222
+ initialDelayMs: shouldPace && frame.length <= packetCapacity ? BLE_WRITE_DELAY_MS : 0,
1223
+ burstSize: shouldPace ? 1 : undefined,
1224
+ burstPauseMs: shouldPace ? BLE_WRITE_DELAY_MS : 0,
1225
+ writePacket: packet => this.writeOnce(uuid, transport.bytesToHex(packet)),
1226
+ });
1227
+ }
1150
1228
  handleNotification(deviceId, hexData) {
1151
1229
  var _a, _b;
1152
1230
  if (hexData === 'PAIRING_REJECTED') {
1153
1231
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
1154
- if (this.runPromise) {
1155
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1232
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1233
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
1234
+ this.rejectProtocolV2Frames(deviceId, error);
1235
+ }
1236
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1156
1237
  this.runPromise.reject(error);
1157
- this.rejectAllProtocolV2Frames(error);
1158
1238
  }
1159
1239
  return;
1160
1240
  }
@@ -1170,13 +1250,8 @@ class ElectronBleTransport {
1170
1250
  this.handleProtocolV1Notification(deviceId, hexData);
1171
1251
  }
1172
1252
  handleProtocolV2Notification(deviceId, hexData) {
1173
- var _a, _b, _c;
1253
+ var _a;
1174
1254
  try {
1175
- if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== deviceId) {
1176
- (_b = this.v2Assemblers.get(deviceId)) === null || _b === void 0 ? void 0 : _b.reset();
1177
- this.resetProtocolV2Frames(deviceId);
1178
- return;
1179
- }
1180
1255
  const bytes = transport.hexToBytes(hexData);
1181
1256
  if (bytes.length === 0)
1182
1257
  return;
@@ -1188,12 +1263,9 @@ class ElectronBleTransport {
1188
1263
  }
1189
1264
  }
1190
1265
  catch (error) {
1191
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] Protocol V2 notification error:', error);
1192
- if (this.runPromise) {
1193
- const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1194
- this.runPromise.reject(notifyError);
1195
- this.rejectAllProtocolV2Frames(notifyError);
1196
- }
1266
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 notification error:', error);
1267
+ const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1268
+ this.rejectProtocolV2Frames(deviceId, notifyError);
1197
1269
  }
1198
1270
  }
1199
1271
  getProtocolV2FrameQueue(uuid) {
@@ -1213,20 +1285,16 @@ class ElectronBleTransport {
1213
1285
  }
1214
1286
  this.getProtocolV2FrameQueue(uuid).push(frame);
1215
1287
  }
1216
- rejectAllProtocolV2Frames(error) {
1217
- this.v2FrameQueues.clear();
1218
- for (const framePromise of this.v2FramePromises.values()) {
1219
- framePromise.reject(error);
1220
- }
1221
- this.v2FramePromises.clear();
1222
- }
1223
1288
  resetProtocolV2Frames(uuid) {
1224
- this.v2FrameQueues.delete(uuid);
1225
- this.v2FramePromises.delete(uuid);
1289
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1226
1290
  }
1227
- isActiveProtocolV2Call(uuid, token) {
1228
- var _a;
1229
- return ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === uuid && this.activeProtocolV2Call.token === token;
1291
+ rejectProtocolV2Frames(uuid, error) {
1292
+ this.v2FrameQueues.delete(uuid);
1293
+ const framePromise = this.v2FramePromises.get(uuid);
1294
+ if (framePromise) {
1295
+ this.v2FramePromises.delete(uuid);
1296
+ framePromise.reject(error);
1297
+ }
1230
1298
  }
1231
1299
  readProtocolV2Frame(uuid) {
1232
1300
  return __awaiter(this, void 0, void 0, function* () {
@@ -1251,17 +1319,20 @@ class ElectronBleTransport {
1251
1319
  const result = this.processProtocolV1Notification(deviceId, hexData);
1252
1320
  if (result.error) {
1253
1321
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1254
- if (this.runPromise) {
1322
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1255
1323
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1256
1324
  }
1257
1325
  return;
1258
1326
  }
1259
- if (result.isComplete && result.completePacket && this.runPromise) {
1327
+ if (result.isComplete &&
1328
+ result.completePacket &&
1329
+ this.runPromise &&
1330
+ this.runPromiseDeviceId === deviceId) {
1260
1331
  this.runPromise.resolve(result.completePacket);
1261
1332
  }
1262
1333
  }
1263
1334
  call(uuid, name, data, options) {
1264
- var _a, _b;
1335
+ var _a;
1265
1336
  return __awaiter(this, void 0, void 0, function* () {
1266
1337
  if (!this._messages) {
1267
1338
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
@@ -1273,12 +1344,8 @@ class ElectronBleTransport {
1273
1344
  if (!protocol) {
1274
1345
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1275
1346
  }
1276
- if (shouldSuppressHighVolumeCallLog(name)) ;
1277
- else if (isLogBlockCommand(name)) {
1278
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
1279
- }
1280
- else {
1281
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
1347
+ if (!transport.shouldSuppressHighVolumeCallLog(name)) {
1348
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('transport call', transport.createTransportCallLog(name, protocol, data));
1282
1349
  }
1283
1350
  if (protocol === 'V2') {
1284
1351
  return this.callProtocolV2(uuid, name, data, options);
@@ -1299,6 +1366,7 @@ class ElectronBleTransport {
1299
1366
  const runPromise = hdShared.createDeferred();
1300
1367
  runPromise.promise.catch(() => undefined);
1301
1368
  this.runPromise = runPromise;
1369
+ this.runPromiseDeviceId = uuid;
1302
1370
  const messages = this._messages;
1303
1371
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1304
1372
  let timeout;
@@ -1337,6 +1405,17 @@ class ElectronBleTransport {
1337
1405
  }
1338
1406
  catch (e) {
1339
1407
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1408
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1409
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1410
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1411
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1412
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1413
+ this.notificationCleanups.delete(uuid);
1414
+ this.notificationTokens.delete(uuid);
1415
+ if (!isProbeTimeout) {
1416
+ yield this.releaseNative(uuid);
1417
+ }
1418
+ }
1340
1419
  throw e;
1341
1420
  }
1342
1421
  finally {
@@ -1344,81 +1423,64 @@ class ElectronBleTransport {
1344
1423
  clearTimeout(timeout);
1345
1424
  if (this.runPromise === runPromise) {
1346
1425
  this.runPromise = null;
1426
+ this.runPromiseDeviceId = null;
1347
1427
  }
1348
1428
  }
1349
1429
  });
1350
1430
  }
1351
1431
  callProtocolV2(uuid, name, data, options) {
1352
- var _a, _b, _c, _d, _e;
1432
+ var _a;
1353
1433
  return __awaiter(this, void 0, void 0, function* () {
1354
1434
  if (!this._messages || !this._messagesV2) {
1355
1435
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1356
1436
  }
1357
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
1358
- if (this.runPromise) {
1359
- if (!forceRun) {
1360
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1361
- }
1362
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1363
- this.runPromise.reject(error);
1364
- this.rejectAllProtocolV2Frames(error);
1365
- this.runPromise = null;
1366
- this.activeProtocolV2Call = null;
1367
- }
1368
- const runPromise = hdShared.createDeferred();
1369
- runPromise.promise.catch(() => undefined);
1370
- this.runPromise = runPromise;
1371
- const callToken = this.nextProtocolV2CallToken++;
1372
- this.activeProtocolV2Call = { uuid, token: callToken };
1373
- (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1374
- this.resetProtocolV2Frames(uuid);
1375
- let completed = false;
1376
- 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 });
1437
+ const callOptions = options;
1377
1438
  try {
1378
- const session = new transport.ProtocolV2Session({
1379
- schemas: {
1380
- protocolV1: this._messages,
1381
- protocolV2: this._messagesV2,
1382
- },
1383
- router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1384
- writeFrame: (frame) => this.writeWithChunking(uuid, transport.bytesToHex(frame)),
1385
- readFrame: () => __awaiter(this, void 0, void 0, function* () {
1386
- const rxFrame = yield this.readProtocolV2Frame(uuid);
1387
- if (!(rxFrame instanceof Uint8Array)) {
1388
- throw new Error('Response is not Uint8Array');
1389
- }
1390
- return rxFrame;
1391
- }),
1392
- logger: this.Log,
1393
- logPrefix: 'ProtocolV2 BLE',
1394
- createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
1395
- });
1396
- const result = yield session.call(name, data, callOptions);
1397
- completed = true;
1398
- return result;
1439
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1399
1440
  }
1400
1441
  catch (e) {
1401
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1402
- (_c = this.v2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
1403
- this.resetProtocolV2Frames(uuid);
1404
- }
1405
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] Protocol V2 call error:', e);
1442
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 call error:', e);
1406
1443
  throw e;
1407
1444
  }
1408
- finally {
1409
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1410
- if (!completed) {
1411
- (_e = this.v2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1412
- }
1413
- this.resetProtocolV2Frames(uuid);
1414
- this.activeProtocolV2Call = null;
1415
- }
1416
- if (this.runPromise === runPromise) {
1417
- this.runPromise = null;
1418
- }
1419
- }
1420
1445
  });
1421
1446
  }
1447
+ createProtocolV2Adapter(uuid) {
1448
+ var _a;
1449
+ const generation = (_a = this.notificationTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1450
+ const assertCurrentGeneration = () => {
1451
+ if (this.notificationTokens.get(uuid) !== generation) {
1452
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
1453
+ }
1454
+ };
1455
+ return {
1456
+ router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1457
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1458
+ generation,
1459
+ prepareCall: () => {
1460
+ var _a;
1461
+ assertCurrentGeneration();
1462
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1463
+ this.resetProtocolV2Frames(uuid);
1464
+ },
1465
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
1466
+ readFrame: () => __awaiter(this, void 0, void 0, function* () {
1467
+ assertCurrentGeneration();
1468
+ const rxFrame = yield this.readProtocolV2Frame(uuid);
1469
+ if (!(rxFrame instanceof Uint8Array)) {
1470
+ throw new Error('Response is not Uint8Array');
1471
+ }
1472
+ return rxFrame;
1473
+ }),
1474
+ reset: (reason) => {
1475
+ var _a;
1476
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1477
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
1478
+ },
1479
+ logger: this.Log,
1480
+ logPrefix: 'ProtocolV2 BLE',
1481
+ createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
1482
+ };
1483
+ }
1422
1484
  processProtocolV1Notification(deviceId, hexData) {
1423
1485
  try {
1424
1486
  if (typeof hexData !== 'string') {