@onekeyfe/hd-transport-react-native 1.2.0-alpha.16 → 1.2.0-alpha.160

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
@@ -93,7 +93,8 @@ const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
93
93
 
94
94
  const IOS_PACKET_LENGTH = 128;
95
95
  const ANDROID_PACKET_LENGTH = 192;
96
- const ANDROID_DEFAULT_MTU = 23;
96
+ const IOS_PROTOCOL_V2_PACKET_LENGTH = 244;
97
+ const ANDROID_PROTOCOL_V2_PACKET_LENGTH = 244;
97
98
  const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
98
99
  const OneKeyServices = {
99
100
  classic: {
@@ -117,36 +118,38 @@ const getInfosForServiceUuid = (serviceUuid, deviceType) => {
117
118
  return null;
118
119
  }
119
120
  const normalizedServiceUuid = normalizeBleUuid(serviceUuid);
120
- const service = (_a = services[serviceUuid]) !== null && _a !== void 0 ? _a : Object.values(services).find(item => normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid);
121
+ const service = (_a = services[serviceUuid]) !== null && _a !== void 0 ? _a : Object.values(services).find(item => normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid ||
122
+ hdShared.matchesKnownBleUuid(serviceUuid, hdShared.createKnownBleUuidAliases(item.serviceUuid)));
121
123
  if (!service) {
122
124
  return null;
123
125
  }
124
126
  return service;
125
127
  };
126
128
  const normalizeBleUuid = (uuid) => (uuid !== null && uuid !== void 0 ? uuid : '').replace(/-/g, '').toLowerCase();
127
- const getBleUuidKey = (uuid) => {
128
- const normalized = normalizeBleUuid(uuid);
129
- return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
130
- };
131
129
  const isSameBleUuid = (left, right) => {
132
- const normalizedLeft = normalizeBleUuid(left);
133
- const normalizedRight = normalizeBleUuid(right);
134
- return (normalizedLeft === normalizedRight ||
135
- (getBleUuidKey(left) !== '' && getBleUuidKey(left) === getBleUuidKey(right)));
130
+ if (!left || !right)
131
+ return false;
132
+ return hdShared.matchesKnownBleUuid(left, hdShared.createKnownBleUuidAliases(right));
136
133
  };
137
134
 
138
135
  function hasWritableCapability(characteristic) {
139
136
  return !!(characteristic.isWritableWithResponse || characteristic.isWritableWithoutResponse);
140
137
  }
141
- function resolveProtocolV2PacketCapacity({ platform, iosPacketLength = IOS_PACKET_LENGTH, androidPacketLength = ANDROID_PACKET_LENGTH, mtu, }) {
142
- if (platform === 'ios') {
143
- return iosPacketLength;
144
- }
145
- if (platform === 'android') {
146
- const payloadLength = Math.max((mtu !== null && mtu !== void 0 ? mtu : ANDROID_DEFAULT_MTU) - 3, 1);
147
- return Math.min(androidPacketLength, payloadLength);
148
- }
149
- return androidPacketLength;
138
+ function resolveProtocolV2PacketCapacity({ platform, iosPacketLength = IOS_PROTOCOL_V2_PACKET_LENGTH, androidPacketLength = ANDROID_PROTOCOL_V2_PACKET_LENGTH, mtu, }) {
139
+ const negotiatedMtu = typeof mtu === 'number' && Number.isFinite(mtu) && mtu > 3 ? Math.floor(mtu) : 23;
140
+ const payloadLength = negotiatedMtu - 3;
141
+ const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
142
+ return Math.min(configuredPacketLength, payloadLength);
143
+ }
144
+ function shouldRefreshNegotiatedMtu(mtu) {
145
+ return typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 23;
146
+ }
147
+ function shouldWriteProtocolV2WithResponse({ platform, highThroughput, requestedWithResponse, characteristic, }) {
148
+ if (!characteristic.isWritableWithResponse)
149
+ return false;
150
+ if (!characteristic.isWritableWithoutResponse)
151
+ return true;
152
+ return requestedWithResponse === true || (platform === 'ios' && !highThroughput);
150
153
  }
151
154
 
152
155
  const timer = process.env.NODE_ENV === 'development'
@@ -191,6 +194,40 @@ const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject)
191
194
  }, ms);
192
195
  });
193
196
 
197
+ const ATT_INSUFFICIENT_AUTHENTICATION = 5;
198
+ const ATT_UNLIKELY_ERROR = 14;
199
+ const ATT_INSUFFICIENT_ENCRYPTION = 15;
200
+ const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
201
+ const nativeErrorText = (error) => [error.reason, error.message]
202
+ .filter((value) => typeof value === 'string')
203
+ .join(' ');
204
+ const isNativeBleStaleBondError = (error) => {
205
+ if (!error || typeof error !== 'object') {
206
+ return typeof error === 'string' ? hdShared.isBleStaleBondErrorText(error) : false;
207
+ }
208
+ const nativeError = error;
209
+ if (nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
210
+ nativeError.attErrorCode === ATT_UNLIKELY_ERROR ||
211
+ nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
212
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION) {
213
+ return true;
214
+ }
215
+ return hdShared.isBleStaleBondErrorText(nativeErrorText(nativeError));
216
+ };
217
+ const toBleStaleBondHardwareError = (error) => {
218
+ if (hdShared.isBleStaleBondHardwareError(error)) {
219
+ return error;
220
+ }
221
+ const nativeError = (error !== null && error !== void 0 ? error : {});
222
+ const text = nativeErrorText(nativeError);
223
+ const peerRemoved = nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
224
+ nativeError.attErrorCode === ATT_UNLIKELY_ERROR ||
225
+ text.includes('Peer removed pairing information');
226
+ return hdShared.ERRORS.TypedError(peerRemoved
227
+ ? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
228
+ : hdShared.HardwareErrorCode.BleDeviceBondError, text || undefined);
229
+ };
230
+
194
231
  const isHeaderChunk = (chunk) => {
195
232
  if (chunk.length < 9)
196
233
  return false;
@@ -203,59 +240,20 @@ const isHeaderChunk = (chunk) => {
203
240
  return false;
204
241
  };
205
242
 
206
- const Log$1 = bleLogger;
207
243
  class BleTransport {
208
244
  constructor(device, writeCharacteristic, notifyCharacteristic) {
209
245
  this.name = 'ReactNativeBleTransport';
210
- this.mtuSize = 23;
211
246
  this.id = device.id;
212
247
  this.device = device;
213
248
  this.writeCharacteristic = writeCharacteristic;
214
249
  this.notifyCharacteristic = notifyCharacteristic;
215
250
  }
216
- writeWithRetry(data, retryCount = BleTransport.MAX_RETRIES) {
251
+ writeWithRetry(data) {
217
252
  return __awaiter(this, void 0, void 0, function* () {
218
- try {
219
- yield this.writeCharacteristic.writeWithoutResponse(data);
220
- }
221
- catch (error) {
222
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`Write retry attempt ${BleTransport.MAX_RETRIES - retryCount + 1}, error: ${error}`);
223
- if (retryCount > 0) {
224
- yield hdShared.wait(BleTransport.RETRY_DELAY);
225
- if (error.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
226
- error.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
227
- try {
228
- yield this.device.connect();
229
- yield this.device.discoverAllServicesAndCharacteristics();
230
- }
231
- catch (e) {
232
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`Connect or discoverAllServicesAndCharacteristics error: ${e}`);
233
- }
234
- }
235
- else {
236
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`writeCharacteristic error: ${error}`);
237
- }
238
- return this.writeWithRetry(data, retryCount - 1);
239
- }
240
- throw error;
241
- }
253
+ yield this.writeCharacteristic.writeWithoutResponse(data);
242
254
  });
243
255
  }
244
256
  }
245
- BleTransport.MAX_RETRIES = 5;
246
- BleTransport.RETRY_DELAY = 2000;
247
-
248
- function createTransportCallLog(name, protocol, data) {
249
- if (name === 'ResourceUpdate' || name === 'ResourceAck') {
250
- return {
251
- name,
252
- protocol,
253
- file_name: data.file_name,
254
- hash: data.hash,
255
- };
256
- }
257
- return { name, protocol };
258
- }
259
257
 
260
258
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
261
259
  const Log = bleLogger;
@@ -264,10 +262,35 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
264
262
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
265
263
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
266
264
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
267
- const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
268
265
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
269
266
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
270
267
  const ANDROID_GATT_CONGESTED_STATUS = 143;
268
+ const isAsciiWhitespace = (code) => code === 0x09 ||
269
+ code === 0x0a ||
270
+ code === 0x0b ||
271
+ code === 0x0c ||
272
+ code === 0x0d ||
273
+ code === 0x20;
274
+ const hasGattCongestedStatus = (text) => {
275
+ let searchFrom = 0;
276
+ while (searchFrom < text.length) {
277
+ const statusIndex = text.indexOf('status', searchFrom);
278
+ if (statusIndex < 0)
279
+ return false;
280
+ let cursor = statusIndex + 'status'.length;
281
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
282
+ cursor += 1;
283
+ if (text[cursor] === ':' || text[cursor] === '=') {
284
+ cursor += 1;
285
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
286
+ cursor += 1;
287
+ }
288
+ if (text.startsWith(String(ANDROID_GATT_CONGESTED_STATUS), cursor))
289
+ return true;
290
+ searchFrom = statusIndex + 'status'.length;
291
+ }
292
+ return false;
293
+ };
271
294
  const delay = (ms) => new Promise(resolve => {
272
295
  setTimeout(resolve, ms);
273
296
  });
@@ -275,10 +298,6 @@ const getFirmwareUploadWriteRetryType = (error) => {
275
298
  if (!error || typeof error !== 'object')
276
299
  return null;
277
300
  const bleWriteError = error;
278
- if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
279
- bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
280
- return 'reconnectable';
281
- }
282
301
  if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
283
302
  bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
284
303
  return 'congested';
@@ -286,18 +305,24 @@ const getFirmwareUploadWriteRetryType = (error) => {
286
305
  const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
287
306
  .filter(value => typeof value === 'string')
288
307
  .join(' ');
289
- return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
308
+ return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
290
309
  };
291
310
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
292
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
293
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
311
+ const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
294
312
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
295
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
313
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
314
+ const BLE_NATIVE_TEARDOWN_TIMEOUT_MS = 3000;
315
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
316
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
317
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
318
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
319
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
320
+ const DEVICE_SCAN_TIMEOUT_MS = 3000;
296
321
  const IOS_NOTIFY_READY_DELAY_MS = 150;
297
322
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
298
323
  const DEFAULT_PROTOCOL_V2_BLE_TUNING = {
299
- iosPacketLength: IOS_PACKET_LENGTH,
300
- androidPacketLength: ANDROID_PACKET_LENGTH,
324
+ iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
325
+ androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
301
326
  };
302
327
  let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
303
328
  const normalizePositiveInteger = (value, fallback) => {
@@ -320,25 +345,37 @@ function resetProtocolV2BleTuning() {
320
345
  function getProtocolV2BleTuning() {
321
346
  return Object.assign({}, protocolV2BleTuning);
322
347
  }
323
- function inferProtocolHintFromDeviceName(name) {
324
- return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
325
- }
326
348
  function getDeviceDisplayName(device) {
327
349
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
328
350
  }
329
- function isGenericBleService(uuid) {
330
- return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
331
- }
332
- function hasKnownOneKeyService(device) {
333
- var _a;
334
- return ((_a = device === null || device === void 0 ? void 0 : device.serviceUUIDs) !== null && _a !== void 0 ? _a : []).some(serviceUuid => getInfosForServiceUuid(serviceUuid, 'classic'));
335
- }
336
- const ANDROID_REQUEST_MTU = 256;
351
+ const IOS_REQUEST_MTU = 247;
352
+ const ANDROID_REQUEST_MTU = 517;
353
+ const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
354
+ const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
355
+ const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
356
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
337
357
  const connectOptions = {
338
- requestMTU: ANDROID_REQUEST_MTU,
339
- timeout: 3000,
358
+ requestMTU: getRequestedBleMtu(),
359
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
340
360
  refreshGatt: 'OnConnected',
341
361
  };
362
+ const fallbackConnectOptions = {
363
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
364
+ };
365
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
366
+ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
367
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
368
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
369
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
370
+ const BLE_SETUP_WEDGED_MESSAGE = 'BLE setup wedged repeatedly';
371
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
372
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
373
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
374
+ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.PollingTimeout &&
375
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
376
+ error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
377
+ const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
378
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
342
379
  const tryToGetConfiguration = (device) => {
343
380
  if (!device || !device.serviceUUIDs)
344
381
  return null;
@@ -350,29 +387,30 @@ const tryToGetConfiguration = (device) => {
350
387
  return null;
351
388
  return infos;
352
389
  };
353
- const requestAndroidMtu = (device) => __awaiter(void 0, void 0, void 0, function* () {
354
- if (reactNative.Platform.OS !== 'android')
390
+ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
391
+ if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
355
392
  return device;
356
393
  try {
357
- const mtuDevice = yield device.requestMTU(ANDROID_REQUEST_MTU);
358
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU configured', {
359
- deviceId: device.id,
360
- requested: ANDROID_REQUEST_MTU,
361
- actual: mtuDevice.mtu,
362
- });
394
+ const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
363
395
  return mtuDevice;
364
396
  }
365
397
  catch (error) {
366
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
398
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
399
+ platform: reactNative.Platform.OS,
400
+ stage,
401
+ attempt,
402
+ actual: device.mtu,
403
+ error: error instanceof Error ? error.message : String(error),
404
+ });
367
405
  return device;
368
406
  }
369
407
  });
408
+ const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
370
409
  function remapError(error) {
371
410
  var _a;
372
411
  if (error instanceof reactNativeBlePlx.BleError) {
373
- if (error.iosErrorCode === reactNativeBlePlx.BleATTErrorCode.UnlikelyError ||
374
- error.reason === 'Peer removed pairing information') {
375
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation);
412
+ if (isNativeBleStaleBondError(error)) {
413
+ throw toBleStaleBondHardwareError(error);
376
414
  }
377
415
  if ((error === null || error === void 0 ? void 0 : error.attErrorCode) === 22) {
378
416
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondError);
@@ -393,9 +431,17 @@ class ReactNativeBleTransport {
393
431
  this.stopped = false;
394
432
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
395
433
  this.runPromise = null;
434
+ this.runPromiseDeviceId = null;
396
435
  this.firmwareUploadWriteRecoveryIds = new Set();
397
436
  this.deviceProtocol = new Map();
437
+ this.probingProtocols = new Map();
438
+ this.writeTimeoutCounts = new Map();
439
+ this.connectionSetupTimeoutCounts = new Map();
398
440
  this.deviceProtocolHints = new Map();
441
+ this.sessionProtocols = new Map();
442
+ this.confirmedProtocolV2 = new Set();
443
+ this.protocolReprobeFailures = new Map();
444
+ this.staleBondErrors = new Map();
399
445
  this.protocolV2Assemblers = new Map();
400
446
  this.protocolV2FrameQueues = new Map();
401
447
  this.protocolV2FramePromises = new Map();
@@ -416,12 +462,17 @@ class ReactNativeBleTransport {
416
462
  this.rejectProtocolV2Frames(uuid, new Error(reason));
417
463
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
418
464
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
419
- yield this.release(uuid, true);
465
+ yield this.releaseNative(uuid, true);
420
466
  }
421
467
  }),
422
468
  });
423
469
  this.monitorTokens = new Map();
470
+ this.disconnectEventTokens = new Map();
471
+ this.protocolV2HighVolumeLogSignatures = new Map();
472
+ this.androidHighPriorityDevices = new Set();
473
+ this.androidPriorityResetTimers = new Map();
424
474
  this.nextMonitorToken = 1;
475
+ this.lifecycleOperations = new Map();
425
476
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
426
477
  }
427
478
  init(logger, emitter) {
@@ -434,10 +485,18 @@ class ReactNativeBleTransport {
434
485
  this._messages = messages;
435
486
  }
436
487
  configureProtocolV2(signedData) {
488
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
489
+ if (this.protocolV2SchemaConfiguration === configuration) {
490
+ return;
491
+ }
492
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
437
493
  this._messagesV2 = parseConfigure(signedData);
438
- this.protocolV2Links
439
- .invalidateAllLinks('Protocol V2 schema reconfigured')
440
- .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
494
+ this.protocolV2SchemaConfiguration = configuration;
495
+ if (isReconfiguration) {
496
+ this.protocolV2Links
497
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
498
+ .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
499
+ }
441
500
  }
442
501
  listen() {
443
502
  }
@@ -448,7 +507,6 @@ class ReactNativeBleTransport {
448
507
  return Promise.resolve(this.blePlxManager);
449
508
  }
450
509
  resolveCharacteristics(device) {
451
- var _a, _b, _c, _d;
452
510
  return __awaiter(this, void 0, void 0, function* () {
453
511
  yield device.discoverAllServicesAndCharacteristics();
454
512
  let infos = tryToGetConfiguration(device);
@@ -465,19 +523,11 @@ class ReactNativeBleTransport {
465
523
  }
466
524
  }
467
525
  }
468
- let fallbackServiceUuid;
469
526
  if (!infos) {
470
527
  const services = yield device.services();
471
528
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:', services === null || services === void 0 ? void 0 : services.map(service => service.uuid));
472
- const knownService = services.find(service => getInfosForServiceUuid(service.uuid, 'classic'));
473
- const fallbackService = (_a = knownService !== null && knownService !== void 0 ? knownService : services.find(service => !isGenericBleService(service.uuid))) !== null && _a !== void 0 ? _a : services[0];
474
- if (fallbackService) {
475
- fallbackServiceUuid = fallbackService.uuid;
476
- characteristics = yield device.characteristicsForService(fallbackService.uuid);
477
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
478
- }
479
529
  }
480
- if (!infos && !fallbackServiceUuid) {
530
+ if (!infos) {
481
531
  try {
482
532
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
483
533
  yield device.cancelConnection();
@@ -487,9 +537,7 @@ class ReactNativeBleTransport {
487
537
  }
488
538
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
489
539
  }
490
- const serviceUuid = (_b = infos === null || infos === void 0 ? void 0 : infos.serviceUuid) !== null && _b !== void 0 ? _b : fallbackServiceUuid;
491
- const writeUuid = (_c = infos === null || infos === void 0 ? void 0 : infos.writeUuid) !== null && _c !== void 0 ? _c : '00000002-0000-1000-8000-00805f9b34fb';
492
- const notifyUuid = (_d = infos === null || infos === void 0 ? void 0 : infos.notifyUuid) !== null && _d !== void 0 ? _d : '00000003-0000-1000-8000-00805f9b34fb';
540
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
493
541
  if (!serviceUuid) {
494
542
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
495
543
  }
@@ -530,8 +578,8 @@ class ReactNativeBleTransport {
530
578
  attachDisconnectSubscription(transport, device, uuid) {
531
579
  var _a;
532
580
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
581
+ const { monitorToken } = transport;
533
582
  transport.disconnectSubscription = device.onDisconnected(() => {
534
- var _a;
535
583
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
536
584
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
537
585
  return;
@@ -540,17 +588,16 @@ class ReactNativeBleTransport {
540
588
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale transport: ', device === null || device === void 0 ? void 0 : device.id);
541
589
  return;
542
590
  }
591
+ if (this.monitorTokens.get(uuid) !== monitorToken) {
592
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale generation: ', device === null || device === void 0 ? void 0 : device.id);
593
+ return;
594
+ }
543
595
  try {
544
596
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
545
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
546
- name: device === null || device === void 0 ? void 0 : device.name,
547
- id: device === null || device === void 0 ? void 0 : device.id,
548
- connectId: device === null || device === void 0 ? void 0 : device.id,
549
- });
550
- if (this.runPromise) {
597
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
598
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
551
599
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
552
600
  this.runPromise.reject(error);
553
- this.rejectAllProtocolV2Frames(error);
554
601
  }
555
602
  }
556
603
  catch (e) {
@@ -561,6 +608,22 @@ class ReactNativeBleTransport {
561
608
  }
562
609
  });
563
610
  }
611
+ emitDeviceDisconnect(uuid, name, token) {
612
+ var _a;
613
+ if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
614
+ return;
615
+ }
616
+ if (this.monitorTokens.get(uuid) !== token) {
617
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect event ignored for stale generation: ', uuid);
618
+ return;
619
+ }
620
+ this.disconnectEventTokens.set(uuid, token);
621
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
622
+ name,
623
+ id: uuid,
624
+ connectId: uuid,
625
+ });
626
+ }
564
627
  reconnectFirmwareUploadTransport(uuid, transport) {
565
628
  var _a, _b;
566
629
  return __awaiter(this, void 0, void 0, function* () {
@@ -574,19 +637,19 @@ class ReactNativeBleTransport {
574
637
  const isConnected = yield device.isConnected().catch(() => false);
575
638
  if (!isConnected) {
576
639
  try {
577
- device = yield device.connect(connectOptions);
640
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
578
641
  }
579
642
  catch (e) {
580
643
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
581
644
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
582
- device = yield device.connect();
645
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
583
646
  }
584
647
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
585
648
  throw e;
586
649
  }
587
650
  }
588
651
  }
589
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
652
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
590
653
  transport.device = device;
591
654
  transport.writeCharacteristic = writeCharacteristic;
592
655
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -634,7 +697,7 @@ class ReactNativeBleTransport {
634
697
  allowDuplicates: true,
635
698
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
636
699
  }, (error, device) => {
637
- var _a, _b, _c;
700
+ var _a, _b;
638
701
  if (error) {
639
702
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
640
703
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
@@ -655,9 +718,14 @@ class ReactNativeBleTransport {
655
718
  return;
656
719
  }
657
720
  const displayName = getDeviceDisplayName(device);
658
- const isOneKey = hdShared.isOnekeyDevice((_b = device === null || device === void 0 ? void 0 : device.name) !== null && _b !== void 0 ? _b : null, device === null || device === void 0 ? void 0 : device.id) ||
659
- hdShared.isOnekeyDevice((_c = device === null || device === void 0 ? void 0 : device.localName) !== null && _c !== void 0 ? _c : null, device === null || device === void 0 ? void 0 : device.id) ||
660
- hasKnownOneKeyService(device);
721
+ const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
722
+ const isOneKey = !isUnnamedIOSPeripheral &&
723
+ hdShared.isOnekeyBluetoothDevice({
724
+ id: device === null || device === void 0 ? void 0 : device.id,
725
+ name: device === null || device === void 0 ? void 0 : device.name,
726
+ localName: device === null || device === void 0 ? void 0 : device.localName,
727
+ serviceUuids: (_b = device === null || device === void 0 ? void 0 : device.serviceUUIDs) !== null && _b !== void 0 ? _b : getBluetoothServiceUuids(),
728
+ });
661
729
  if (isOneKey) {
662
730
  addDevice(device);
663
731
  }
@@ -672,10 +740,15 @@ class ReactNativeBleTransport {
672
740
  });
673
741
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
674
742
  for (const device of devices) {
675
- const { serviceUUIDs } = device;
676
- const hasCachedServiceUuid = Boolean(serviceUUIDs === null || serviceUUIDs === void 0 ? void 0 : serviceUUIDs.length);
677
- const keepDevice = reactNative.Platform.OS === 'ios' || hasCachedServiceUuid;
678
- if (keepDevice) {
743
+ const localName = 'localName' in device && typeof device.localName === 'string'
744
+ ? device.localName
745
+ : null;
746
+ if (hdShared.isOnekeyBluetoothDevice({
747
+ id: device.id,
748
+ name: device.name,
749
+ localName,
750
+ serviceUuids: device.serviceUUIDs,
751
+ })) {
679
752
  Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
680
753
  addDevice(device);
681
754
  }
@@ -685,16 +758,11 @@ class ReactNativeBleTransport {
685
758
  var _a;
686
759
  if (deviceList.every(d => d.id !== device.id)) {
687
760
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
688
- const protocolHint = inferProtocolHintFromDeviceName(displayName);
689
- if (protocolHint) {
690
- this.deviceProtocolHints.set(device.id, protocolHint);
691
- }
692
761
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
693
762
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
694
763
  deviceId: device.id,
695
764
  name: displayName,
696
765
  serviceUUIDs: device.serviceUUIDs,
697
- protocolHint,
698
766
  });
699
767
  }
700
768
  };
@@ -705,13 +773,70 @@ class ReactNativeBleTransport {
705
773
  }));
706
774
  });
707
775
  }
776
+ installTransportForAcquire(uuid, device, characteristics) {
777
+ return __awaiter(this, void 0, void 0, function* () {
778
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
779
+ const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
780
+ transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
781
+ const monitorToken = this.nextMonitorToken;
782
+ this.nextMonitorToken += 1;
783
+ const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
784
+ transport$1.monitorToken = monitorToken;
785
+ transport$1.notifyTransactionId = notifyTransactionId;
786
+ this.monitorTokens.set(uuid, monitorToken);
787
+ transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
788
+ transportCache[uuid] = transport$1;
789
+ this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
790
+ this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
791
+ if (reactNative.Platform.OS === 'ios') {
792
+ yield new Promise(resolve => {
793
+ setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
794
+ });
795
+ }
796
+ else if (reactNative.Platform.OS === 'android') {
797
+ yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
798
+ }
799
+ const initialMtu = transport$1.mtuSize;
800
+ let refreshAttempts = 0;
801
+ if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
802
+ shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
803
+ refreshAttempts += 1;
804
+ let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
805
+ transport$1.device = refreshedDevice;
806
+ transport$1.mtuSize =
807
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
808
+ if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
809
+ yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
810
+ refreshAttempts += 1;
811
+ refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
812
+ transport$1.device = refreshedDevice;
813
+ transport$1.mtuSize =
814
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
815
+ }
816
+ }
817
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
818
+ platform: reactNative.Platform.OS,
819
+ requested: getRequestedBleMtu(),
820
+ initial: initialMtu,
821
+ actual: transport$1.mtuSize,
822
+ refreshAttempts,
823
+ });
824
+ return transport$1;
825
+ });
826
+ }
708
827
  acquire(input) {
709
- var _a, _b;
710
828
  return __awaiter(this, void 0, void 0, function* () {
711
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
829
+ const { uuid } = input;
712
830
  if (!uuid) {
713
831
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
714
832
  }
833
+ return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
834
+ });
835
+ }
836
+ acquireUnlocked(input) {
837
+ var _a;
838
+ return __awaiter(this, void 0, void 0, function* () {
839
+ const { uuid, forceCleanRunPromise, expectedProtocol } = input;
715
840
  const cachedTransport = transportCache[uuid];
716
841
  if (cachedTransport) {
717
842
  const cachedProtocol = this.deviceProtocol.get(uuid);
@@ -723,14 +848,14 @@ class ReactNativeBleTransport {
723
848
  return { uuid, protocolType: cachedProtocol };
724
849
  }
725
850
  Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
726
- yield this.release(uuid, true);
851
+ yield this.releaseUnlocked(uuid, true);
727
852
  }
728
853
  let device = null;
729
854
  if (forceCleanRunPromise && this.runPromise) {
730
855
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
731
856
  this.runPromise.reject(error);
732
- this.rejectAllProtocolV2Frames(error);
733
857
  this.runPromise = null;
858
+ this.runPromiseDeviceId = null;
734
859
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
735
860
  }
736
861
  const blePlxManager = yield this.getPlxManager();
@@ -763,14 +888,17 @@ class ReactNativeBleTransport {
763
888
  if (!device) {
764
889
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
765
890
  try {
766
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
891
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
767
892
  }
768
893
  catch (e) {
769
894
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
895
+ if (shouldRethrowBleSetupError(e)) {
896
+ throw e;
897
+ }
770
898
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
771
899
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
772
900
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
773
- device = yield blePlxManager.connectToDevice(uuid);
901
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
774
902
  }
775
903
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
776
904
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -786,23 +914,27 @@ class ReactNativeBleTransport {
786
914
  }
787
915
  if (!(yield device.isConnected())) {
788
916
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
917
+ const disconnectedDevice = device;
789
918
  try {
790
- device = yield device.connect(connectOptions);
919
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
791
920
  }
792
921
  catch (e) {
793
922
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
923
+ if (shouldRethrowBleSetupError(e)) {
924
+ throw e;
925
+ }
794
926
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
795
927
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
796
928
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
797
929
  try {
798
- device = yield device.connect();
930
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
799
931
  }
800
932
  catch (e) {
801
933
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
802
934
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
803
935
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
804
- yield device.cancelConnection();
805
- device = yield device.connect();
936
+ yield disconnectedDevice.cancelConnection();
937
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
806
938
  }
807
939
  }
808
940
  }
@@ -811,51 +943,47 @@ class ReactNativeBleTransport {
811
943
  }
812
944
  }
813
945
  }
814
- device = yield requestAndroidMtu(device);
815
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
946
+ device = yield resolveNegotiatedMtu(device);
947
+ const acquiredDevice = device;
948
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
816
949
  const protocolHint = expectedProtocol
817
950
  ? undefined
818
- : (_a = this.deviceProtocolHints.get(uuid)) !== null && _a !== void 0 ? _a : inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
819
- yield this.release(uuid, true);
951
+ : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
952
+ yield this.releaseUnlocked(uuid, true);
820
953
  if (protocolHint) {
821
954
  this.deviceProtocolHints.set(uuid, protocolHint);
822
955
  }
823
- const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
824
- if (reactNative.Platform.OS === 'android') {
825
- transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
826
- }
827
- const monitorToken = this.nextMonitorToken;
828
- this.nextMonitorToken += 1;
829
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
830
- transport$1.monitorToken = monitorToken;
831
- transport$1.notifyTransactionId = notifyTransactionId;
832
- this.monitorTokens.set(uuid, monitorToken);
833
- transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
834
- transportCache[uuid] = transport$1;
835
- this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
836
- if (reactNative.Platform.OS === 'ios') {
837
- yield new Promise(resolve => {
838
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
839
- });
956
+ yield this.installTransportForAcquire(uuid, acquiredDevice, {
957
+ writeCharacteristic,
958
+ notifyCharacteristic,
959
+ });
960
+ try {
961
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
962
+ yield this.installTransportForAcquire(uuid, acquiredDevice);
963
+ }));
964
+ const currentTransport = transportCache[uuid];
965
+ if (!currentTransport) {
966
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
967
+ }
968
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
969
+ return { uuid, protocolType };
840
970
  }
841
- else if (reactNative.Platform.OS === 'android') {
842
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
971
+ catch (error) {
972
+ if (hdShared.isBleStaleBondHardwareError(error)) {
973
+ yield this.disconnectUnlocked(uuid);
974
+ }
975
+ else {
976
+ yield this.releaseUnlocked(uuid, true);
977
+ }
978
+ throw error;
843
979
  }
844
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
845
- (_b = this.emitter) === null || _b === void 0 ? void 0 : _b.emit('device-connect', {
846
- name: device.name,
847
- id: device.id,
848
- connectId: device.id,
849
- });
850
- this.attachDisconnectSubscription(transport$1, device, uuid);
851
- return { uuid, protocolType };
852
980
  });
853
981
  }
854
982
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
855
983
  let bufferLength = 0;
856
984
  let buffer$1 = [];
857
985
  const subscription = characteristic.monitor((error, c) => {
858
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
986
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
859
987
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
860
988
  if (error) {
861
989
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -867,46 +995,42 @@ class ReactNativeBleTransport {
867
995
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
868
996
  return;
869
997
  }
870
- if (this.deviceProtocol.get(uuid) === 'V2') {
998
+ if (isNativeBleStaleBondError(error)) {
999
+ this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
1000
+ return;
1001
+ }
1002
+ if (this.getActiveProtocol(uuid) === 'V2') {
871
1003
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
872
1004
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
873
1005
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
874
1006
  }
875
- else if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
876
- errorCode = hdShared.HardwareErrorCode.BleDeviceBondError;
877
- }
878
- else if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
879
- ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('Cannot find client characteristic config descriptor')) ||
880
- ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('The handle is invalid')) ||
881
- ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('Writing is not permitted')) ||
882
- ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('notify change failed for device'))) {
1007
+ else if (((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Cannot write client characteristic config descriptor')) ||
1008
+ ((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot find client characteristic config descriptor')) ||
1009
+ ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('The handle is invalid')) ||
1010
+ ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('Writing is not permitted')) ||
1011
+ ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('notify change failed for device'))) {
883
1012
  errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
884
1013
  }
885
1014
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
886
1015
  return;
887
1016
  }
888
- if (this.runPromise) {
1017
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
889
1018
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
890
- if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
1019
+ if ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('The connection has timed out unexpectedly')) {
891
1020
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
892
1021
  }
893
- if ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Encryption is insufficient')) {
894
- ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
895
- }
896
- if (((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('Cannot write client characteristic config descriptor')) ||
897
- ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Cannot find client characteristic config descriptor')) ||
898
- ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('The handle is invalid')) ||
899
- ((_o = error.reason) === null || _o === void 0 ? void 0 : _o.includes('Writing is not permitted')) ||
900
- ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
1022
+ if (((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('Cannot write client characteristic config descriptor')) ||
1023
+ ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Cannot find client characteristic config descriptor')) ||
1024
+ ((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('The handle is invalid')) ||
1025
+ ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Writing is not permitted')) ||
1026
+ ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('notify change failed for device'))) {
901
1027
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
902
1028
  this.runPromise.reject(notifyError);
903
- this.rejectAllProtocolV2Frames(notifyError);
904
1029
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
905
1030
  return;
906
1031
  }
907
1032
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
908
1033
  this.runPromise.reject(notifyError);
909
- this.rejectAllProtocolV2Frames(notifyError);
910
1034
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
911
1035
  }
912
1036
  return;
@@ -920,7 +1044,7 @@ class ReactNativeBleTransport {
920
1044
  }
921
1045
  try {
922
1046
  const data = buffer.Buffer.from(c.value, 'base64');
923
- const protocol = this.deviceProtocol.get(uuid);
1047
+ const protocol = this.getActiveProtocol(uuid);
924
1048
  if (!protocol) {
925
1049
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
926
1050
  return;
@@ -940,32 +1064,46 @@ class ReactNativeBleTransport {
940
1064
  const value = buffer.Buffer.from(buffer$1);
941
1065
  bufferLength = 0;
942
1066
  buffer$1 = [];
943
- (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1067
+ if (this.runPromiseDeviceId === uuid) {
1068
+ (_o = this.runPromise) === null || _o === void 0 ? void 0 : _o.resolve(value.toString('hex'));
1069
+ }
944
1070
  }
945
1071
  }
946
1072
  catch (error) {
947
1073
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
948
1074
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
949
- if (this.deviceProtocol.get(uuid) === 'V2') {
1075
+ if (this.getActiveProtocol(uuid) === 'V2') {
950
1076
  this.rejectProtocolV2Frames(uuid, notifyError);
951
1077
  }
952
- else {
953
- (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
1078
+ else if (this.runPromiseDeviceId === uuid) {
1079
+ (_p = this.runPromise) === null || _p === void 0 ? void 0 : _p.reject(notifyError);
954
1080
  }
955
1081
  }
956
1082
  }, notifyTransactionId);
957
1083
  return subscription;
958
1084
  }
959
1085
  release(uuid, onclose = false) {
960
- var _a, _b, _c, _d, _e, _f, _g;
961
1086
  return __awaiter(this, void 0, void 0, function* () {
962
- const transport = transportCache[uuid];
1087
+ return this.runLifecycleOperation(uuid, () => this.releaseUnlocked(uuid, onclose));
1088
+ });
1089
+ }
1090
+ releaseUnlocked(uuid, onclose = false) {
1091
+ return __awaiter(this, void 0, void 0, function* () {
963
1092
  yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
964
- if (this.runPromise) {
1093
+ return this.releaseNative(uuid, onclose);
1094
+ });
1095
+ }
1096
+ releaseNative(uuid, onclose = false) {
1097
+ var _a, _b, _c, _d, _e;
1098
+ return __awaiter(this, void 0, void 0, function* () {
1099
+ const transport = transportCache[uuid];
1100
+ const manager = this.blePlxManager;
1101
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
965
1102
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
966
1103
  this.runPromise.reject(error);
967
1104
  this.runPromise = null;
968
- this.rejectAllProtocolV2Frames(error);
1105
+ this.runPromiseDeviceId = null;
1106
+ this.rejectProtocolV2Frames(uuid, error);
969
1107
  }
970
1108
  else {
971
1109
  this.resetProtocolV2Frames(uuid);
@@ -985,31 +1123,38 @@ class ReactNativeBleTransport {
985
1123
  Log === null || Log === void 0 ? void 0 : Log.debug('release: removing notify subscription, characteristic: ', (_c = transport.notifyCharacteristic) === null || _c === void 0 ? void 0 : _c.uuid);
986
1124
  (_d = transport.notifySubscription) === null || _d === void 0 ? void 0 : _d.remove();
987
1125
  transport.notifySubscription = undefined;
988
- if (transport.notifyTransactionId) {
989
- try {
990
- yield ((_e = this.blePlxManager) === null || _e === void 0 ? void 0 : _e.cancelTransaction(transport.notifyTransactionId));
991
- }
992
- catch (e) {
993
- Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel notify transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
994
- }
1126
+ if (transportCache[uuid] === transport) {
1127
+ delete transportCache[uuid];
995
1128
  }
996
- delete transportCache[uuid];
997
1129
  }
1130
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
998
1131
  this.deviceProtocol.delete(uuid);
999
- (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1132
+ this.probingProtocols.delete(uuid);
1133
+ this.staleBondErrors.delete(uuid);
1134
+ (_e = this.protocolV2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1000
1135
  this.protocolV2Assemblers.delete(uuid);
1001
1136
  this.resetProtocolV2Frames(uuid);
1002
- try {
1003
- yield ((_g = this.blePlxManager) === null || _g === void 0 ? void 0 : _g.cancelTransaction(uuid));
1004
- }
1005
- catch (e) {
1006
- Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
1007
- }
1137
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1138
+ const operations = [
1139
+ this.runBestEffortNativeOperation('release: restore connection priority', () => this.restoreAndroidConnectionPriority(uuid, transport)),
1140
+ ];
1141
+ if ((transport === null || transport === void 0 ? void 0 : transport.notifyTransactionId) && manager) {
1142
+ operations.push(this.runBestEffortNativeOperation('release: cancel notify transaction', () => manager.cancelTransaction(transport.notifyTransactionId)));
1143
+ }
1144
+ if (manager) {
1145
+ operations.push(this.runBestEffortNativeOperation('release: cancel transaction', () => manager.cancelTransaction(uuid)));
1146
+ }
1147
+ yield Promise.all(operations);
1148
+ }));
1008
1149
  return Promise.resolve(true);
1009
1150
  });
1010
1151
  }
1011
1152
  post(session, name, data) {
1012
1153
  return __awaiter(this, void 0, void 0, function* () {
1154
+ if (this.getProtocolType(session) === 'V2') {
1155
+ yield this.protocolV2Links.sendFlowControl(session, () => this.createProtocolV2Adapter(session), name, data);
1156
+ return;
1157
+ }
1013
1158
  yield this.call(session, name, data);
1014
1159
  });
1015
1160
  }
@@ -1025,7 +1170,6 @@ class ReactNativeBleTransport {
1025
1170
  if (!protocol) {
1026
1171
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1027
1172
  }
1028
- Log === null || Log === void 0 ? void 0 : Log.debug('transport call', createTransportCallLog(name, protocol, data));
1029
1173
  if (protocol === 'V2') {
1030
1174
  return this.callProtocolV2(uuid, name, data, options);
1031
1175
  }
@@ -1044,7 +1188,19 @@ class ReactNativeBleTransport {
1044
1188
  const transport = this.getCachedTransport(uuid);
1045
1189
  const runPromise = hdShared.createDeferred();
1046
1190
  runPromise.promise.catch(() => undefined);
1191
+ const supersededRunPromise = this.runPromise;
1192
+ if (supersededRunPromise) {
1193
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1194
+ }
1047
1195
  this.runPromise = runPromise;
1196
+ this.runPromiseDeviceId = uuid;
1197
+ const releaseOwnershipIfCurrent = () => {
1198
+ if (this.runPromise === runPromise) {
1199
+ this.runPromise = null;
1200
+ this.runPromiseDeviceId = null;
1201
+ }
1202
+ };
1203
+ const isCurrentOwner = () => this.runPromise === runPromise;
1048
1204
  const messages = this._messages;
1049
1205
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1050
1206
  let timeout;
@@ -1065,6 +1221,12 @@ class ReactNativeBleTransport {
1065
1221
  }
1066
1222
  catch (e) {
1067
1223
  onError(e);
1224
+ if (isWedgedWriteError(e)) {
1225
+ throw e;
1226
+ }
1227
+ if (isNativeBleStaleBondError(e) || hdShared.isBleStaleBondHardwareError(e)) {
1228
+ throw toBleStaleBondHardwareError(e);
1229
+ }
1068
1230
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1069
1231
  }
1070
1232
  }
@@ -1092,6 +1254,12 @@ class ReactNativeBleTransport {
1092
1254
  }
1093
1255
  catch (e) {
1094
1256
  onError(e);
1257
+ if (isWedgedWriteError(e)) {
1258
+ throw e;
1259
+ }
1260
+ if (isNativeBleStaleBondError(e) || hdShared.isBleStaleBondHardwareError(e)) {
1261
+ throw toBleStaleBondHardwareError(e);
1262
+ }
1095
1263
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1096
1264
  }
1097
1265
  }
@@ -1102,8 +1270,8 @@ class ReactNativeBleTransport {
1102
1270
  });
1103
1271
  }
1104
1272
  if (name === 'EmmcFileWrite') {
1105
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1106
- this.runPromise = null;
1273
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1274
+ releaseOwnershipIfCurrent();
1107
1275
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1108
1276
  });
1109
1277
  }
@@ -1119,7 +1287,7 @@ class ReactNativeBleTransport {
1119
1287
  let attempt = 0;
1120
1288
  while (true) {
1121
1289
  try {
1122
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1290
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1123
1291
  return;
1124
1292
  }
1125
1293
  catch (error) {
@@ -1127,36 +1295,18 @@ class ReactNativeBleTransport {
1127
1295
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1128
1296
  throw error;
1129
1297
  }
1130
- const shouldReconnect = retryType === 'reconnectable';
1131
- const delayMs = shouldReconnect
1132
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1133
- : resolveFirmwareUploadRetryDelay(attempt);
1298
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1134
1299
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1135
1300
  attempt: attempt + 1,
1136
1301
  delayMs,
1137
- reconnect: shouldReconnect,
1138
1302
  error,
1139
1303
  });
1140
- if (shouldReconnect) {
1141
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1142
- }
1143
1304
  yield delay(delayMs);
1144
1305
  attempt += 1;
1145
- if (shouldReconnect) {
1146
- try {
1147
- yield this.reconnectFirmwareUploadTransport(uuid, transport);
1148
- }
1149
- catch (e) {
1150
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
1151
- if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1152
- throw e;
1153
- }
1154
- }
1155
- }
1156
1306
  }
1157
1307
  }
1158
1308
  }), e => {
1159
- this.runPromise = null;
1309
+ releaseOwnershipIfCurrent();
1160
1310
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1161
1311
  });
1162
1312
  }
@@ -1164,11 +1314,22 @@ class ReactNativeBleTransport {
1164
1314
  for (const o of buffers) {
1165
1315
  const outData = o.toString('base64');
1166
1316
  try {
1167
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1317
+ const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1318
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1319
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1320
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1168
1321
  }
1169
1322
  catch (e) {
1170
1323
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1171
- this.runPromise = null;
1324
+ releaseOwnershipIfCurrent();
1325
+ if (isWedgedWriteError(e)) {
1326
+ throw e;
1327
+ }
1328
+ if (isNativeBleStaleBondError(e) || hdShared.isBleStaleBondHardwareError(e)) {
1329
+ const bondError = toBleStaleBondHardwareError(e);
1330
+ this.rememberStaleBondError(uuid, bondError);
1331
+ throw bondError;
1332
+ }
1172
1333
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1173
1334
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1174
1335
  }
@@ -1201,12 +1362,19 @@ class ReactNativeBleTransport {
1201
1362
  return check.call(jsonData);
1202
1363
  }
1203
1364
  catch (e) {
1204
- if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1205
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
1365
+ if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1366
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1206
1367
  }
1207
1368
  else {
1208
1369
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1209
1370
  }
1371
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1372
+ const isStaleCall = this.runPromise !== runPromise;
1373
+ if (!isProbeTimeout &&
1374
+ !isStaleCall &&
1375
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1376
+ yield this.disconnect(uuid);
1377
+ }
1210
1378
  throw e;
1211
1379
  }
1212
1380
  finally {
@@ -1214,6 +1382,7 @@ class ReactNativeBleTransport {
1214
1382
  clearTimeout(timeout);
1215
1383
  if (this.runPromise === runPromise) {
1216
1384
  this.runPromise = null;
1385
+ this.runPromiseDeviceId = null;
1217
1386
  }
1218
1387
  }
1219
1388
  });
@@ -1222,10 +1391,17 @@ class ReactNativeBleTransport {
1222
1391
  this.stopped = true;
1223
1392
  }
1224
1393
  disconnect(session) {
1225
- var _a, _b, _c, _d, _e;
1394
+ return __awaiter(this, void 0, void 0, function* () {
1395
+ return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
1396
+ });
1397
+ }
1398
+ disconnectUnlocked(session) {
1399
+ var _a, _b, _c;
1226
1400
  return __awaiter(this, void 0, void 0, function* () {
1227
1401
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1228
1402
  const transport = transportCache[session];
1403
+ const manager = this.blePlxManager;
1404
+ const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
1229
1405
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1230
1406
  try {
1231
1407
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1238,7 +1414,7 @@ class ReactNativeBleTransport {
1238
1414
  }
1239
1415
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1240
1416
  try {
1241
- Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing notify subscription, characteristic: ', (_a = transport.notifyCharacteristic) === null || _a === void 0 ? void 0 : _a.uuid);
1417
+ Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing notify subscription, characteristic: ', (_b = transport.notifyCharacteristic) === null || _b === void 0 ? void 0 : _b.uuid);
1242
1418
  transport.notifySubscription.remove();
1243
1419
  transport.notifySubscription = undefined;
1244
1420
  }
@@ -1246,52 +1422,203 @@ class ReactNativeBleTransport {
1246
1422
  Log === null || Log === void 0 ? void 0 : Log.error('disconnect: remove notify subscription error: ', e);
1247
1423
  }
1248
1424
  }
1249
- if (session) {
1250
- try {
1251
- yield ((_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelTransaction(session));
1252
- }
1253
- catch (e) {
1254
- Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
1255
- }
1256
- }
1257
- if (transport === null || transport === void 0 ? void 0 : transport.device) {
1258
- try {
1259
- yield transport.device.cancelConnection();
1260
- }
1261
- catch (e) {
1262
- Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: device.cancelConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
1263
- }
1264
- }
1265
- try {
1266
- yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelDeviceConnection(session));
1267
- }
1268
- catch (e) {
1269
- Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: manager.cancelDeviceConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
1270
- }
1271
- if (transportCache[session]) {
1425
+ if (!transport || transportCache[session] === transport) {
1272
1426
  delete transportCache[session];
1273
1427
  }
1274
1428
  this.deviceProtocol.delete(session);
1429
+ this.probingProtocols.delete(session);
1430
+ this.staleBondErrors.delete(session);
1275
1431
  this.deviceProtocolHints.delete(session);
1432
+ this.sessionProtocols.delete(session);
1433
+ this.protocolReprobeFailures.delete(session);
1276
1434
  this.protocolV2Assemblers.delete(session);
1277
1435
  this.resetProtocolV2Frames(session);
1278
1436
  try {
1279
- (_d = this.emitter) === null || _d === void 0 ? void 0 : _d.emit('device-disconnect', {
1280
- name: (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name,
1281
- id: session,
1282
- connectId: session,
1283
- });
1437
+ this.emitDeviceDisconnect(session, (_c = transport === null || transport === void 0 ? void 0 : transport.device) === null || _c === void 0 ? void 0 : _c.name, monitorToken);
1284
1438
  }
1285
1439
  catch (e) {
1286
1440
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1287
1441
  }
1442
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1443
+ this.monitorTokens.delete(session);
1444
+ }
1445
+ yield this.runNativeTeardown(session, manager, () => __awaiter(this, void 0, void 0, function* () {
1446
+ const operations = [];
1447
+ if (manager) {
1448
+ operations.push(this.runBestEffortNativeOperation('disconnect: cancel transaction', () => manager.cancelTransaction(session)));
1449
+ operations.push(this.runBestEffortNativeOperation('disconnect: cancel device connection', () => manager.cancelDeviceConnection(session)));
1450
+ }
1451
+ if (transport === null || transport === void 0 ? void 0 : transport.device) {
1452
+ operations.push(this.runBestEffortNativeOperation('disconnect: device cancel connection', () => transport.device.cancelConnection()));
1453
+ }
1454
+ yield Promise.all(operations);
1455
+ }));
1288
1456
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1289
1457
  });
1290
1458
  }
1459
+ runNativeTeardown(uuid, manager, teardown) {
1460
+ return __awaiter(this, void 0, void 0, function* () {
1461
+ let timer;
1462
+ let timedOut = false;
1463
+ const pending = Promise.resolve()
1464
+ .then(teardown)
1465
+ .catch(error => {
1466
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE native teardown error (ignored): ', (error === null || error === void 0 ? void 0 : error.message) || error);
1467
+ });
1468
+ try {
1469
+ yield Promise.race([
1470
+ pending,
1471
+ new Promise(resolve => {
1472
+ timer = setTimeout(() => {
1473
+ timedOut = true;
1474
+ resolve();
1475
+ }, BLE_NATIVE_TEARDOWN_TIMEOUT_MS);
1476
+ }),
1477
+ ]);
1478
+ }
1479
+ finally {
1480
+ if (timer)
1481
+ clearTimeout(timer);
1482
+ }
1483
+ if (timedOut) {
1484
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE native teardown timed out:', uuid);
1485
+ if (this.blePlxManager === manager) {
1486
+ this.resetPlxManager();
1487
+ }
1488
+ }
1489
+ });
1490
+ }
1491
+ runBestEffortNativeOperation(label, operation) {
1492
+ return Promise.resolve()
1493
+ .then(operation)
1494
+ .catch(error => {
1495
+ Log === null || Log === void 0 ? void 0 : Log.debug(`${label} error (ignored): `, (error === null || error === void 0 ? void 0 : error.message) || error);
1496
+ });
1497
+ }
1498
+ runLifecycleOperation(uuid, operation) {
1499
+ var _a;
1500
+ return __awaiter(this, void 0, void 0, function* () {
1501
+ const previousOperation = (_a = this.lifecycleOperations.get(uuid)) !== null && _a !== void 0 ? _a : Promise.resolve();
1502
+ let completeOperation;
1503
+ const operationGate = new Promise(resolve => {
1504
+ completeOperation = resolve;
1505
+ });
1506
+ const operationTail = previousOperation.catch(() => undefined).then(() => operationGate);
1507
+ this.lifecycleOperations.set(uuid, operationTail);
1508
+ yield previousOperation.catch(() => undefined);
1509
+ try {
1510
+ return yield operation();
1511
+ }
1512
+ finally {
1513
+ completeOperation();
1514
+ if (this.lifecycleOperations.get(uuid) === operationTail) {
1515
+ this.lifecycleOperations.delete(uuid);
1516
+ }
1517
+ }
1518
+ });
1519
+ }
1291
1520
  cancel() {
1292
1521
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1293
1522
  if (this.runPromise) ;
1294
1523
  this.runPromise = null;
1524
+ this.runPromiseDeviceId = null;
1525
+ }
1526
+ connectWithTimeout(uuid, connect) {
1527
+ return __awaiter(this, void 0, void 0, function* () {
1528
+ let timer;
1529
+ let timedOut = false;
1530
+ const pending = connect();
1531
+ pending.catch(() => undefined);
1532
+ try {
1533
+ const result = yield Promise.race([
1534
+ pending,
1535
+ new Promise((_, reject) => {
1536
+ timer = setTimeout(() => {
1537
+ timedOut = true;
1538
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1539
+ }, BLE_CONNECT_TIMEOUT_MS);
1540
+ }),
1541
+ ]);
1542
+ return result;
1543
+ }
1544
+ catch (error) {
1545
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1546
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1547
+ if (resetManager) {
1548
+ throw this.createWedgedBleSetupError();
1549
+ }
1550
+ }
1551
+ throw error;
1552
+ }
1553
+ finally {
1554
+ if (timer)
1555
+ clearTimeout(timer);
1556
+ }
1557
+ });
1558
+ }
1559
+ resolveCharacteristicsWithTimeout(uuid, device) {
1560
+ return __awaiter(this, void 0, void 0, function* () {
1561
+ let timer;
1562
+ let timedOut = false;
1563
+ const pending = this.resolveCharacteristics(device);
1564
+ pending.catch(() => undefined);
1565
+ try {
1566
+ const result = yield Promise.race([
1567
+ pending,
1568
+ new Promise((_, reject) => {
1569
+ timer = setTimeout(() => {
1570
+ timedOut = true;
1571
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1572
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1573
+ }),
1574
+ ]);
1575
+ this.connectionSetupTimeoutCounts.delete(uuid);
1576
+ return result;
1577
+ }
1578
+ catch (error) {
1579
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1580
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1581
+ if (resetManager) {
1582
+ throw this.createWedgedBleSetupError();
1583
+ }
1584
+ }
1585
+ throw error;
1586
+ }
1587
+ finally {
1588
+ if (timer)
1589
+ clearTimeout(timer);
1590
+ }
1591
+ });
1592
+ }
1593
+ abandonStalledConnection(uuid, stage) {
1594
+ var _a, _b;
1595
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1596
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1597
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1598
+ stage,
1599
+ setupTimeoutsSinceSuccess: timeouts,
1600
+ });
1601
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1602
+ });
1603
+ const stalled = transportCache[uuid];
1604
+ if (stalled) {
1605
+ delete transportCache[uuid];
1606
+ }
1607
+ this.deviceProtocol.delete(uuid);
1608
+ this.probingProtocols.delete(uuid);
1609
+ this.staleBondErrors.delete(uuid);
1610
+ this.protocolV2Assemblers.delete(uuid);
1611
+ this.resetProtocolV2Frames(uuid);
1612
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1613
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1614
+ this.resetPlxManager();
1615
+ this.connectionSetupTimeoutCounts.delete(uuid);
1616
+ return true;
1617
+ }
1618
+ return false;
1619
+ }
1620
+ createWedgedBleSetupError() {
1621
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.PollingTimeout, BLE_SETUP_WEDGED_MESSAGE);
1295
1622
  }
1296
1623
  getCachedTransport(uuid) {
1297
1624
  const transport = transportCache[uuid];
@@ -1300,22 +1627,149 @@ class ReactNativeBleTransport {
1300
1627
  }
1301
1628
  return transport;
1302
1629
  }
1303
- createProtocolMismatchError(expected) {
1304
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1630
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1631
+ return __awaiter(this, void 0, void 0, function* () {
1632
+ let timer;
1633
+ let timedOut = false;
1634
+ try {
1635
+ yield Promise.race([
1636
+ write(data),
1637
+ new Promise((_, reject) => {
1638
+ timer = setTimeout(() => {
1639
+ timedOut = true;
1640
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1641
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1642
+ }),
1643
+ ]);
1644
+ this.writeTimeoutCounts.delete(uuid);
1645
+ }
1646
+ catch (error) {
1647
+ if (timedOut) {
1648
+ if (isCurrentOwner && !isCurrentOwner()) {
1649
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1650
+ }
1651
+ else {
1652
+ this.tearDownWedgedLink(uuid);
1653
+ }
1654
+ }
1655
+ throw error;
1656
+ }
1657
+ finally {
1658
+ if (timer)
1659
+ clearTimeout(timer);
1660
+ }
1661
+ });
1662
+ }
1663
+ tearDownWedgedLink(uuid) {
1664
+ var _a;
1665
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1666
+ this.writeTimeoutCounts.set(uuid, timeouts);
1667
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1668
+ consecutiveWriteTimeouts: timeouts,
1669
+ });
1670
+ const wedged = transportCache[uuid];
1671
+ this.disconnect(uuid).catch(error => {
1672
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1673
+ });
1674
+ if (wedged && transportCache[uuid] === wedged) {
1675
+ delete transportCache[uuid];
1676
+ }
1677
+ this.deviceProtocol.delete(uuid);
1678
+ this.probingProtocols.delete(uuid);
1679
+ this.staleBondErrors.delete(uuid);
1680
+ this.protocolV2Assemblers.delete(uuid);
1681
+ this.resetProtocolV2Frames(uuid);
1682
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1683
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1684
+ this.resetPlxManager();
1685
+ this.writeTimeoutCounts.delete(uuid);
1686
+ }
1687
+ }
1688
+ resetPlxManager() {
1689
+ const manager = this.blePlxManager;
1690
+ this.blePlxManager = undefined;
1691
+ const reason = 'React Native BLE manager reset';
1692
+ Object.entries(transportCache).forEach(([uuid, cachedTransport]) => {
1693
+ var _a, _b, _c, _d;
1694
+ try {
1695
+ (_a = cachedTransport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
1696
+ }
1697
+ catch (error) {
1698
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect subscription removal failed:', error);
1699
+ }
1700
+ cachedTransport.disconnectSubscription = undefined;
1701
+ try {
1702
+ (_b = cachedTransport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
1703
+ }
1704
+ catch (error) {
1705
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset notify subscription removal failed:', error);
1706
+ }
1707
+ cachedTransport.notifySubscription = undefined;
1708
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
1709
+ try {
1710
+ this.emitDeviceDisconnect(uuid, (_c = cachedTransport.device) === null || _c === void 0 ? void 0 : _c.name, (_d = cachedTransport.monitorToken) !== null && _d !== void 0 ? _d : this.monitorTokens.get(uuid));
1711
+ }
1712
+ catch (error) {
1713
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect event failed:', error);
1714
+ }
1715
+ delete transportCache[uuid];
1716
+ });
1717
+ this.protocolV2Links.invalidateAllLinks(reason).catch(error => {
1718
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager link invalidation failed:', error);
1719
+ });
1720
+ this.deviceProtocol.clear();
1721
+ this.probingProtocols.clear();
1722
+ this.staleBondErrors.clear();
1723
+ this.sessionProtocols.clear();
1724
+ this.confirmedProtocolV2.clear();
1725
+ this.protocolReprobeFailures.clear();
1726
+ this.writeTimeoutCounts.clear();
1727
+ this.connectionSetupTimeoutCounts.clear();
1728
+ this.monitorTokens.clear();
1729
+ this.protocolV2Assemblers.clear();
1730
+ try {
1731
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1732
+ }
1733
+ catch (error) {
1734
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1735
+ }
1736
+ }
1737
+ createProtocolMismatchError(expected, uuid) {
1738
+ const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1739
+ return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1305
1740
  }
1306
1741
  createProtocolDetectionError() {
1307
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1742
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1308
1743
  }
1309
1744
  clearProbeProtocol(uuid, protocol) {
1745
+ if (this.probingProtocols.get(uuid) === protocol) {
1746
+ this.probingProtocols.delete(uuid);
1747
+ }
1310
1748
  if (this.deviceProtocol.get(uuid) === protocol) {
1311
1749
  this.deviceProtocol.delete(uuid);
1312
1750
  }
1313
1751
  }
1314
- detectProtocol(uuid, expectedProtocol, protocolHint) {
1752
+ getActiveProtocol(uuid) {
1753
+ var _a;
1754
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1755
+ }
1756
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1757
+ var _a;
1315
1758
  return __awaiter(this, void 0, void 0, function* () {
1759
+ if (reactNative.Platform.OS === 'ios' && expectedProtocol === 'V1') {
1760
+ this.deviceProtocol.set(uuid, expectedProtocol);
1761
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1762
+ deviceId: uuid,
1763
+ protocol: expectedProtocol,
1764
+ source: 'expected',
1765
+ });
1766
+ return expectedProtocol;
1767
+ }
1768
+ this.throwIfStaleBondError(uuid);
1316
1769
  if (expectedProtocol === 'V1') {
1317
1770
  if (yield this.probeProtocolV1(uuid)) {
1318
1771
  this.deviceProtocol.set(uuid, 'V1');
1772
+ this.sessionProtocols.set(uuid, 'V1');
1319
1773
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1320
1774
  deviceId: uuid,
1321
1775
  protocol: 'V1',
@@ -1323,26 +1777,48 @@ class ReactNativeBleTransport {
1323
1777
  });
1324
1778
  return 'V1';
1325
1779
  }
1326
- throw this.createProtocolMismatchError(expectedProtocol);
1780
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
1327
1781
  }
1328
1782
  if (expectedProtocol === 'V2') {
1329
- this.deviceProtocol.set(uuid, 'V2');
1330
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1331
- deviceId: uuid,
1332
- protocol: 'V2',
1333
- source: 'expected',
1334
- });
1335
- return 'V2';
1336
- }
1337
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1783
+ if (yield this.probeProtocolV2(uuid)) {
1784
+ this.deviceProtocol.set(uuid, 'V2');
1785
+ this.sessionProtocols.set(uuid, 'V2');
1786
+ this.confirmedProtocolV2.add(uuid);
1787
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1788
+ deviceId: uuid,
1789
+ protocol: 'V2',
1790
+ source: 'expected',
1791
+ });
1792
+ return 'V2';
1793
+ }
1794
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
1795
+ }
1796
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1797
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1798
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1799
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1800
+ !protocolHint &&
1801
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1802
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1338
1803
  for (let i = 0; i < probeOrder.length; i += 1) {
1339
1804
  const protocol = probeOrder[i];
1340
1805
  if (i > 0) {
1341
1806
  yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1807
+ if (!transportCache[uuid]) {
1808
+ if (!rebuildTransport) {
1809
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1810
+ }
1811
+ yield rebuildTransport();
1812
+ }
1342
1813
  }
1343
1814
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1344
1815
  if (detected) {
1345
1816
  this.deviceProtocol.set(uuid, protocol);
1817
+ this.sessionProtocols.set(uuid, protocol);
1818
+ if (protocol === 'V2') {
1819
+ this.confirmedProtocolV2.add(uuid);
1820
+ }
1821
+ this.protocolReprobeFailures.delete(uuid);
1346
1822
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1347
1823
  deviceId: uuid,
1348
1824
  protocol,
@@ -1351,7 +1827,14 @@ class ReactNativeBleTransport {
1351
1827
  return protocol;
1352
1828
  }
1353
1829
  }
1830
+ if (trustSessionProtocol) {
1831
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1832
+ }
1833
+ else {
1834
+ this.protocolReprobeFailures.delete(uuid);
1835
+ }
1354
1836
  this.deviceProtocol.delete(uuid);
1837
+ this.probingProtocols.delete(uuid);
1355
1838
  throw this.createProtocolDetectionError();
1356
1839
  });
1357
1840
  }
@@ -1403,13 +1886,17 @@ class ReactNativeBleTransport {
1403
1886
  return false;
1404
1887
  }
1405
1888
  try {
1406
- this.deviceProtocol.set(uuid, 'V1');
1407
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1889
+ this.probingProtocols.set(uuid, 'V1');
1890
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1891
+ this.probingProtocols.delete(uuid);
1408
1892
  return true;
1409
1893
  }
1410
1894
  catch (error) {
1411
1895
  this.clearProbeProtocol(uuid, 'V1');
1412
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1896
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1897
+ if (isWedgedWriteError(error) || hdShared.isBleStaleBondHardwareError(error)) {
1898
+ throw error;
1899
+ }
1413
1900
  return false;
1414
1901
  }
1415
1902
  });
@@ -1420,8 +1907,9 @@ class ReactNativeBleTransport {
1420
1907
  if (!this._messages || !this._messagesV2) {
1421
1908
  return false;
1422
1909
  }
1423
- this.deviceProtocol.set(uuid, 'V2');
1910
+ this.probingProtocols.set(uuid, 'V2');
1424
1911
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1912
+ this.throwIfStaleBondError(uuid);
1425
1913
  const detected = yield transport.probeProtocolV2({
1426
1914
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
1427
1915
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
@@ -1432,10 +1920,14 @@ class ReactNativeBleTransport {
1432
1920
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1433
1921
  this.resetProtocolV2Frames(uuid);
1434
1922
  },
1923
+ shouldRethrow: hdShared.isBleStaleBondHardwareError,
1435
1924
  });
1436
1925
  if (!detected) {
1437
1926
  this.clearProbeProtocol(uuid, 'V2');
1438
1927
  }
1928
+ else {
1929
+ this.probingProtocols.delete(uuid);
1930
+ }
1439
1931
  return detected;
1440
1932
  });
1441
1933
  }
@@ -1478,16 +1970,8 @@ class ReactNativeBleTransport {
1478
1970
  }
1479
1971
  this.getProtocolV2FrameQueue(uuid).push(frame);
1480
1972
  }
1481
- rejectAllProtocolV2Frames(error) {
1482
- this.protocolV2FrameQueues.clear();
1483
- for (const framePromise of this.protocolV2FramePromises.values()) {
1484
- framePromise.reject(error);
1485
- }
1486
- this.protocolV2FramePromises.clear();
1487
- }
1488
1973
  resetProtocolV2Frames(uuid) {
1489
- this.protocolV2FrameQueues.delete(uuid);
1490
- this.protocolV2FramePromises.delete(uuid);
1974
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1491
1975
  }
1492
1976
  rejectProtocolV2Frames(uuid, error) {
1493
1977
  this.protocolV2FrameQueues.delete(uuid);
@@ -1497,6 +1981,19 @@ class ReactNativeBleTransport {
1497
1981
  framePromise.reject(error);
1498
1982
  }
1499
1983
  }
1984
+ rememberStaleBondError(uuid, error) {
1985
+ this.staleBondErrors.set(uuid, error);
1986
+ this.rejectProtocolV2Frames(uuid, error);
1987
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1988
+ this.runPromise.reject(error);
1989
+ }
1990
+ }
1991
+ throwIfStaleBondError(uuid) {
1992
+ const error = this.staleBondErrors.get(uuid);
1993
+ if (error) {
1994
+ throw error;
1995
+ }
1996
+ }
1500
1997
  readProtocolV2Frame(uuid) {
1501
1998
  return __awaiter(this, void 0, void 0, function* () {
1502
1999
  const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
@@ -1515,20 +2012,75 @@ class ReactNativeBleTransport {
1515
2012
  }
1516
2013
  });
1517
2014
  }
1518
- writeProtocolV2Frame(transport, frame) {
2015
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
2016
+ return __awaiter(this, void 0, void 0, function* () {
2017
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
2018
+ platform: reactNative.Platform.OS,
2019
+ highThroughput: context.highThroughput,
2020
+ requestedWithResponse: context.writeWithResponse,
2021
+ characteristic: transport.writeCharacteristic,
2022
+ });
2023
+ let attempt = 0;
2024
+ for (;;) {
2025
+ assertCurrentGeneration();
2026
+ if (context.signal.aborted) {
2027
+ throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
2028
+ }
2029
+ try {
2030
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
2031
+ ? transport.writeCharacteristic.writeWithResponse(payload)
2032
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
2033
+ try {
2034
+ assertCurrentGeneration();
2035
+ return !context.signal.aborted;
2036
+ }
2037
+ catch (_a) {
2038
+ return false;
2039
+ }
2040
+ });
2041
+ assertCurrentGeneration();
2042
+ return;
2043
+ }
2044
+ catch (error) {
2045
+ if (isNativeBleStaleBondError(error) || hdShared.isBleStaleBondHardwareError(error)) {
2046
+ const bondError = toBleStaleBondHardwareError(error);
2047
+ this.rememberStaleBondError(uuid, bondError);
2048
+ throw bondError;
2049
+ }
2050
+ if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2051
+ attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2052
+ throw error;
2053
+ }
2054
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
2055
+ attempt += 1;
2056
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
2057
+ name: context.messageName,
2058
+ attempt,
2059
+ delayMs,
2060
+ });
2061
+ yield delay(delayMs);
2062
+ }
2063
+ }
2064
+ });
2065
+ }
2066
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1519
2067
  return __awaiter(this, void 0, void 0, function* () {
1520
2068
  const tuning = getProtocolV2BleTuning();
1521
2069
  const packetCapacity = resolveProtocolV2PacketCapacity({
1522
2070
  platform: reactNative.Platform.OS,
1523
2071
  iosPacketLength: tuning.iosPacketLength,
1524
2072
  androidPacketLength: tuning.androidPacketLength,
1525
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
2073
+ mtu: transport$1.mtuSize,
2074
+ });
2075
+ yield transport.writeProtocolV2BleFrame({
2076
+ frame,
2077
+ packetCapacity,
2078
+ assertActive: assertCurrentGeneration,
2079
+ signal: context.signal,
2080
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
2081
+ wait: delay,
2082
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1526
2083
  });
1527
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1528
- const chunk = frame.slice(offset, offset + packetCapacity);
1529
- const base64 = buffer.Buffer.from(chunk).toString('base64');
1530
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1531
- }
1532
2084
  });
1533
2085
  }
1534
2086
  callProtocolV2(uuid, name, data, options) {
@@ -1537,15 +2089,40 @@ class ReactNativeBleTransport {
1537
2089
  if (!this._messages || !this._messagesV2) {
1538
2090
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1539
2091
  }
1540
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : BLE_RESPONSE_TIMEOUT_MS });
1541
- const highVolumeWrite = transport.LogBlockCommand.has(name);
1542
- if (highVolumeWrite) {
2092
+ const callOptions = options;
2093
+ const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
2094
+ if (highThroughputWrite) {
2095
+ yield this.ensureProtocolV2HighThroughputMtu(uuid);
1543
2096
  const tuning = getProtocolV2BleTuning();
1544
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1545
- name,
1546
- writeMode: 'withoutResponse',
1547
- packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
2097
+ const currentTransport = this.getCachedTransport(uuid);
2098
+ const writeWithResponse = shouldWriteProtocolV2WithResponse({
2099
+ platform: reactNative.Platform.OS,
2100
+ highThroughput: true,
2101
+ requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
2102
+ characteristic: currentTransport.writeCharacteristic,
1548
2103
  });
2104
+ const packetCapacity = resolveProtocolV2PacketCapacity({
2105
+ platform: reactNative.Platform.OS,
2106
+ iosPacketLength: tuning.iosPacketLength,
2107
+ androidPacketLength: tuning.androidPacketLength,
2108
+ mtu: currentTransport.mtuSize,
2109
+ });
2110
+ const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
2111
+ const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
2112
+ const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
2113
+ if (!loggedSignatures.has(logSignature)) {
2114
+ loggedSignatures.add(logSignature);
2115
+ this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
2116
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
2117
+ name,
2118
+ writeMode,
2119
+ reportedMtu: currentTransport.mtuSize,
2120
+ packetCapacity,
2121
+ });
2122
+ }
2123
+ }
2124
+ if (highThroughputWrite) {
2125
+ yield this.enableAndroidHighConnectionPriority(uuid);
1549
2126
  }
1550
2127
  try {
1551
2128
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
@@ -1554,6 +2131,85 @@ class ReactNativeBleTransport {
1554
2131
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1555
2132
  throw e;
1556
2133
  }
2134
+ finally {
2135
+ if (highThroughputWrite) {
2136
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
2137
+ }
2138
+ }
2139
+ });
2140
+ }
2141
+ ensureProtocolV2HighThroughputMtu(uuid) {
2142
+ return __awaiter(this, void 0, void 0, function* () {
2143
+ const transport = this.getCachedTransport(uuid);
2144
+ if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
2145
+ return;
2146
+ const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
2147
+ transport.device = refreshedDevice;
2148
+ transport.mtuSize =
2149
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
2150
+ if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
2151
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`);
2152
+ }
2153
+ });
2154
+ }
2155
+ clearAndroidPriorityResetTimer(uuid) {
2156
+ const timerId = this.androidPriorityResetTimers.get(uuid);
2157
+ if (timerId !== undefined) {
2158
+ clearTimeout(timerId);
2159
+ this.androidPriorityResetTimers.delete(uuid);
2160
+ }
2161
+ }
2162
+ enableAndroidHighConnectionPriority(uuid) {
2163
+ return __awaiter(this, void 0, void 0, function* () {
2164
+ if (reactNative.Platform.OS !== 'android')
2165
+ return;
2166
+ this.clearAndroidPriorityResetTimer(uuid);
2167
+ if (this.androidHighPriorityDevices.has(uuid))
2168
+ return;
2169
+ const transport = transportCache[uuid];
2170
+ if (!transport)
2171
+ return;
2172
+ try {
2173
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
2174
+ this.androidHighPriorityDevices.add(uuid);
2175
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2176
+ priority: 'high',
2177
+ });
2178
+ }
2179
+ catch (error) {
2180
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
2181
+ error: error instanceof Error ? error.message : String(error),
2182
+ });
2183
+ }
2184
+ });
2185
+ }
2186
+ scheduleAndroidBalancedConnectionPriority(uuid) {
2187
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
2188
+ return;
2189
+ this.clearAndroidPriorityResetTimer(uuid);
2190
+ const timerId = setTimeout(() => {
2191
+ this.androidPriorityResetTimers.delete(uuid);
2192
+ this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
2193
+ }, ANDROID_HIGH_PRIORITY_IDLE_MS);
2194
+ this.androidPriorityResetTimers.set(uuid, timerId);
2195
+ }
2196
+ restoreAndroidConnectionPriority(uuid, transport) {
2197
+ return __awaiter(this, void 0, void 0, function* () {
2198
+ this.clearAndroidPriorityResetTimer(uuid);
2199
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
2200
+ return;
2201
+ }
2202
+ try {
2203
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
2204
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2205
+ priority: 'balanced',
2206
+ });
2207
+ }
2208
+ catch (error) {
2209
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
2210
+ error: error instanceof Error ? error.message : String(error),
2211
+ });
2212
+ }
1557
2213
  });
1558
2214
  }
1559
2215
  createProtocolV2Adapter(uuid) {
@@ -1574,10 +2230,10 @@ class ReactNativeBleTransport {
1574
2230
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1575
2231
  this.resetProtocolV2Frames(uuid);
1576
2232
  },
1577
- writeFrame: (frame) => __awaiter(this, void 0, void 0, function* () {
2233
+ writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1578
2234
  assertCurrentGeneration();
1579
2235
  const currentTransport = this.getCachedTransport(uuid);
1580
- yield this.writeProtocolV2Frame(currentTransport, frame);
2236
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1581
2237
  }),
1582
2238
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1583
2239
  assertCurrentGeneration();
@@ -1589,6 +2245,8 @@ class ReactNativeBleTransport {
1589
2245
  }),
1590
2246
  reset: (reason) => {
1591
2247
  var _a;
2248
+ if (this.monitorTokens.get(uuid) !== generation)
2249
+ return;
1592
2250
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1593
2251
  this.rejectProtocolV2Frames(uuid, new Error(reason));
1594
2252
  },
@@ -1598,11 +2256,20 @@ class ReactNativeBleTransport {
1598
2256
  };
1599
2257
  }
1600
2258
  getProtocolType(path) {
1601
- return this.deviceProtocol.get(path);
2259
+ return this.getActiveProtocol(path);
1602
2260
  }
1603
2261
  }
1604
2262
 
2263
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2264
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2265
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2266
+ exports.BLE_NATIVE_TEARDOWN_TIMEOUT_MS = BLE_NATIVE_TEARDOWN_TIMEOUT_MS;
2267
+ exports.BLE_SETUP_WEDGED_MESSAGE = BLE_SETUP_WEDGED_MESSAGE;
2268
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2269
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2270
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1605
2271
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1606
2272
  exports["default"] = ReactNativeBleTransport;
2273
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1607
2274
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1608
2275
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;