@onekeyfe/hd-transport-react-native 1.2.0-alpha.18 → 1.2.0-alpha.180

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'
@@ -203,59 +206,20 @@ const isHeaderChunk = (chunk) => {
203
206
  return false;
204
207
  };
205
208
 
206
- const Log$1 = bleLogger;
207
209
  class BleTransport {
208
210
  constructor(device, writeCharacteristic, notifyCharacteristic) {
209
211
  this.name = 'ReactNativeBleTransport';
210
- this.mtuSize = 23;
211
212
  this.id = device.id;
212
213
  this.device = device;
213
214
  this.writeCharacteristic = writeCharacteristic;
214
215
  this.notifyCharacteristic = notifyCharacteristic;
215
216
  }
216
- writeWithRetry(data, retryCount = BleTransport.MAX_RETRIES) {
217
+ writeWithRetry(data) {
217
218
  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
- }
219
+ yield this.writeCharacteristic.writeWithoutResponse(data);
242
220
  });
243
221
  }
244
222
  }
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
223
 
260
224
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
261
225
  const Log = bleLogger;
@@ -264,10 +228,35 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
264
228
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
265
229
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
266
230
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
267
- const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
268
231
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
269
232
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
270
233
  const ANDROID_GATT_CONGESTED_STATUS = 143;
234
+ const isAsciiWhitespace = (code) => code === 0x09 ||
235
+ code === 0x0a ||
236
+ code === 0x0b ||
237
+ code === 0x0c ||
238
+ code === 0x0d ||
239
+ code === 0x20;
240
+ const hasGattCongestedStatus = (text) => {
241
+ let searchFrom = 0;
242
+ while (searchFrom < text.length) {
243
+ const statusIndex = text.indexOf('status', searchFrom);
244
+ if (statusIndex < 0)
245
+ return false;
246
+ let cursor = statusIndex + 'status'.length;
247
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
248
+ cursor += 1;
249
+ if (text[cursor] === ':' || text[cursor] === '=') {
250
+ cursor += 1;
251
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
252
+ cursor += 1;
253
+ }
254
+ if (text.startsWith(String(ANDROID_GATT_CONGESTED_STATUS), cursor))
255
+ return true;
256
+ searchFrom = statusIndex + 'status'.length;
257
+ }
258
+ return false;
259
+ };
271
260
  const delay = (ms) => new Promise(resolve => {
272
261
  setTimeout(resolve, ms);
273
262
  });
@@ -275,10 +264,6 @@ const getFirmwareUploadWriteRetryType = (error) => {
275
264
  if (!error || typeof error !== 'object')
276
265
  return null;
277
266
  const bleWriteError = error;
278
- if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
279
- bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
280
- return 'reconnectable';
281
- }
282
267
  if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
283
268
  bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
284
269
  return 'congested';
@@ -286,18 +271,24 @@ const getFirmwareUploadWriteRetryType = (error) => {
286
271
  const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
287
272
  .filter(value => typeof value === 'string')
288
273
  .join(' ');
289
- return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
274
+ return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
290
275
  };
291
276
  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;
277
+ const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
294
278
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
295
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
279
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
280
+ const BLE_NATIVE_TEARDOWN_TIMEOUT_MS = 3000;
281
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
282
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
283
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
284
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
285
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
286
+ const DEVICE_SCAN_TIMEOUT_MS = 3000;
296
287
  const IOS_NOTIFY_READY_DELAY_MS = 150;
297
288
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
298
289
  const DEFAULT_PROTOCOL_V2_BLE_TUNING = {
299
- iosPacketLength: IOS_PACKET_LENGTH,
300
- androidPacketLength: ANDROID_PACKET_LENGTH,
290
+ iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
291
+ androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
301
292
  };
302
293
  let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
303
294
  const normalizePositiveInteger = (value, fallback) => {
@@ -320,25 +311,37 @@ function resetProtocolV2BleTuning() {
320
311
  function getProtocolV2BleTuning() {
321
312
  return Object.assign({}, protocolV2BleTuning);
322
313
  }
323
- function inferProtocolHintFromDeviceName(name) {
324
- return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
325
- }
326
314
  function getDeviceDisplayName(device) {
327
315
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
328
316
  }
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;
317
+ const IOS_REQUEST_MTU = 247;
318
+ const ANDROID_REQUEST_MTU = 517;
319
+ const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
320
+ const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
321
+ const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
322
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
337
323
  const connectOptions = {
338
- requestMTU: ANDROID_REQUEST_MTU,
339
- timeout: 3000,
324
+ requestMTU: getRequestedBleMtu(),
325
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
340
326
  refreshGatt: 'OnConnected',
341
327
  };
328
+ const fallbackConnectOptions = {
329
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
330
+ };
331
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
332
+ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
333
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
334
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
335
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
336
+ const BLE_SETUP_WEDGED_MESSAGE = 'BLE setup wedged repeatedly';
337
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
338
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
339
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
340
+ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.PollingTimeout &&
341
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
342
+ error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
343
+ const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
344
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
342
345
  const tryToGetConfiguration = (device) => {
343
346
  if (!device || !device.serviceUUIDs)
344
347
  return null;
@@ -350,23 +353,25 @@ const tryToGetConfiguration = (device) => {
350
353
  return null;
351
354
  return infos;
352
355
  };
353
- const requestAndroidMtu = (device) => __awaiter(void 0, void 0, void 0, function* () {
354
- if (reactNative.Platform.OS !== 'android')
356
+ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
357
+ if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
355
358
  return device;
356
359
  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
- });
360
+ const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
363
361
  return mtuDevice;
364
362
  }
365
363
  catch (error) {
366
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
364
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
365
+ platform: reactNative.Platform.OS,
366
+ stage,
367
+ attempt,
368
+ actual: device.mtu,
369
+ error: error instanceof Error ? error.message : String(error),
370
+ });
367
371
  return device;
368
372
  }
369
373
  });
374
+ const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
370
375
  function remapError(error) {
371
376
  var _a;
372
377
  if (error instanceof reactNativeBlePlx.BleError) {
@@ -393,9 +398,16 @@ class ReactNativeBleTransport {
393
398
  this.stopped = false;
394
399
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
395
400
  this.runPromise = null;
401
+ this.runPromiseDeviceId = null;
396
402
  this.firmwareUploadWriteRecoveryIds = new Set();
397
403
  this.deviceProtocol = new Map();
404
+ this.probingProtocols = new Map();
405
+ this.writeTimeoutCounts = new Map();
406
+ this.connectionSetupTimeoutCounts = new Map();
398
407
  this.deviceProtocolHints = new Map();
408
+ this.sessionProtocols = new Map();
409
+ this.confirmedProtocolV2 = new Set();
410
+ this.protocolReprobeFailures = new Map();
399
411
  this.protocolV2Assemblers = new Map();
400
412
  this.protocolV2FrameQueues = new Map();
401
413
  this.protocolV2FramePromises = new Map();
@@ -416,12 +428,17 @@ class ReactNativeBleTransport {
416
428
  this.rejectProtocolV2Frames(uuid, new Error(reason));
417
429
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
418
430
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
419
- yield this.release(uuid, true);
431
+ yield this.releaseNative(uuid, true);
420
432
  }
421
433
  }),
422
434
  });
423
435
  this.monitorTokens = new Map();
436
+ this.disconnectEventTokens = new Map();
437
+ this.protocolV2HighVolumeLogSignatures = new Map();
438
+ this.androidHighPriorityDevices = new Set();
439
+ this.androidPriorityResetTimers = new Map();
424
440
  this.nextMonitorToken = 1;
441
+ this.lifecycleOperations = new Map();
425
442
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
426
443
  }
427
444
  init(logger, emitter) {
@@ -434,10 +451,18 @@ class ReactNativeBleTransport {
434
451
  this._messages = messages;
435
452
  }
436
453
  configureProtocolV2(signedData) {
454
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
455
+ if (this.protocolV2SchemaConfiguration === configuration) {
456
+ return;
457
+ }
458
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
437
459
  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));
460
+ this.protocolV2SchemaConfiguration = configuration;
461
+ if (isReconfiguration) {
462
+ this.protocolV2Links
463
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
464
+ .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
465
+ }
441
466
  }
442
467
  listen() {
443
468
  }
@@ -448,7 +473,6 @@ class ReactNativeBleTransport {
448
473
  return Promise.resolve(this.blePlxManager);
449
474
  }
450
475
  resolveCharacteristics(device) {
451
- var _a, _b, _c, _d;
452
476
  return __awaiter(this, void 0, void 0, function* () {
453
477
  yield device.discoverAllServicesAndCharacteristics();
454
478
  let infos = tryToGetConfiguration(device);
@@ -465,19 +489,11 @@ class ReactNativeBleTransport {
465
489
  }
466
490
  }
467
491
  }
468
- let fallbackServiceUuid;
469
492
  if (!infos) {
470
493
  const services = yield device.services();
471
494
  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
495
  }
480
- if (!infos && !fallbackServiceUuid) {
496
+ if (!infos) {
481
497
  try {
482
498
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
483
499
  yield device.cancelConnection();
@@ -487,9 +503,7 @@ class ReactNativeBleTransport {
487
503
  }
488
504
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
489
505
  }
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';
506
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
493
507
  if (!serviceUuid) {
494
508
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
495
509
  }
@@ -530,8 +544,8 @@ class ReactNativeBleTransport {
530
544
  attachDisconnectSubscription(transport, device, uuid) {
531
545
  var _a;
532
546
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
547
+ const { monitorToken } = transport;
533
548
  transport.disconnectSubscription = device.onDisconnected(() => {
534
- var _a;
535
549
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
536
550
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
537
551
  return;
@@ -540,17 +554,16 @@ class ReactNativeBleTransport {
540
554
  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
555
  return;
542
556
  }
557
+ if (this.monitorTokens.get(uuid) !== monitorToken) {
558
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale generation: ', device === null || device === void 0 ? void 0 : device.id);
559
+ return;
560
+ }
543
561
  try {
544
562
  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) {
563
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
564
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
551
565
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
552
566
  this.runPromise.reject(error);
553
- this.rejectAllProtocolV2Frames(error);
554
567
  }
555
568
  }
556
569
  catch (e) {
@@ -561,6 +574,22 @@ class ReactNativeBleTransport {
561
574
  }
562
575
  });
563
576
  }
577
+ emitDeviceDisconnect(uuid, name, token) {
578
+ var _a;
579
+ if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
580
+ return;
581
+ }
582
+ if (this.monitorTokens.get(uuid) !== token) {
583
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect event ignored for stale generation: ', uuid);
584
+ return;
585
+ }
586
+ this.disconnectEventTokens.set(uuid, token);
587
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
588
+ name,
589
+ id: uuid,
590
+ connectId: uuid,
591
+ });
592
+ }
564
593
  reconnectFirmwareUploadTransport(uuid, transport) {
565
594
  var _a, _b;
566
595
  return __awaiter(this, void 0, void 0, function* () {
@@ -574,19 +603,19 @@ class ReactNativeBleTransport {
574
603
  const isConnected = yield device.isConnected().catch(() => false);
575
604
  if (!isConnected) {
576
605
  try {
577
- device = yield device.connect(connectOptions);
606
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
578
607
  }
579
608
  catch (e) {
580
609
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
581
610
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
582
- device = yield device.connect();
611
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
583
612
  }
584
613
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
585
614
  throw e;
586
615
  }
587
616
  }
588
617
  }
589
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
618
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
590
619
  transport.device = device;
591
620
  transport.writeCharacteristic = writeCharacteristic;
592
621
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -634,7 +663,7 @@ class ReactNativeBleTransport {
634
663
  allowDuplicates: true,
635
664
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
636
665
  }, (error, device) => {
637
- var _a, _b, _c;
666
+ var _a, _b;
638
667
  if (error) {
639
668
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
640
669
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
@@ -655,9 +684,14 @@ class ReactNativeBleTransport {
655
684
  return;
656
685
  }
657
686
  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);
687
+ const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
688
+ const isOneKey = !isUnnamedIOSPeripheral &&
689
+ hdShared.isOnekeyBluetoothDevice({
690
+ id: device === null || device === void 0 ? void 0 : device.id,
691
+ name: device === null || device === void 0 ? void 0 : device.name,
692
+ localName: device === null || device === void 0 ? void 0 : device.localName,
693
+ serviceUuids: (_b = device === null || device === void 0 ? void 0 : device.serviceUUIDs) !== null && _b !== void 0 ? _b : getBluetoothServiceUuids(),
694
+ });
661
695
  if (isOneKey) {
662
696
  addDevice(device);
663
697
  }
@@ -672,10 +706,15 @@ class ReactNativeBleTransport {
672
706
  });
673
707
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
674
708
  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) {
709
+ const localName = 'localName' in device && typeof device.localName === 'string'
710
+ ? device.localName
711
+ : null;
712
+ if (hdShared.isOnekeyBluetoothDevice({
713
+ id: device.id,
714
+ name: device.name,
715
+ localName,
716
+ serviceUuids: device.serviceUUIDs,
717
+ })) {
679
718
  Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
680
719
  addDevice(device);
681
720
  }
@@ -685,16 +724,11 @@ class ReactNativeBleTransport {
685
724
  var _a;
686
725
  if (deviceList.every(d => d.id !== device.id)) {
687
726
  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
727
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
693
728
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
694
729
  deviceId: device.id,
695
730
  name: displayName,
696
731
  serviceUUIDs: device.serviceUUIDs,
697
- protocolHint,
698
732
  });
699
733
  }
700
734
  };
@@ -705,32 +739,113 @@ class ReactNativeBleTransport {
705
739
  }));
706
740
  });
707
741
  }
742
+ installTransportForAcquire(uuid, device, characteristics) {
743
+ return __awaiter(this, void 0, void 0, function* () {
744
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
745
+ const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
746
+ transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
747
+ const monitorToken = this.nextMonitorToken;
748
+ this.nextMonitorToken += 1;
749
+ const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
750
+ transport$1.monitorToken = monitorToken;
751
+ transport$1.notifyTransactionId = notifyTransactionId;
752
+ this.monitorTokens.set(uuid, monitorToken);
753
+ transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
754
+ transportCache[uuid] = transport$1;
755
+ this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
756
+ this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
757
+ if (reactNative.Platform.OS === 'ios') {
758
+ yield new Promise(resolve => {
759
+ setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
760
+ });
761
+ }
762
+ else if (reactNative.Platform.OS === 'android') {
763
+ yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
764
+ }
765
+ const initialMtu = transport$1.mtuSize;
766
+ let refreshAttempts = 0;
767
+ if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
768
+ shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
769
+ refreshAttempts += 1;
770
+ let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
771
+ transport$1.device = refreshedDevice;
772
+ transport$1.mtuSize =
773
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
774
+ if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
775
+ yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
776
+ refreshAttempts += 1;
777
+ refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
778
+ transport$1.device = refreshedDevice;
779
+ transport$1.mtuSize =
780
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
781
+ }
782
+ }
783
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
784
+ platform: reactNative.Platform.OS,
785
+ requested: getRequestedBleMtu(),
786
+ initial: initialMtu,
787
+ actual: transport$1.mtuSize,
788
+ refreshAttempts,
789
+ });
790
+ return transport$1;
791
+ });
792
+ }
708
793
  acquire(input) {
709
- var _a, _b;
710
794
  return __awaiter(this, void 0, void 0, function* () {
711
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
795
+ const { uuid } = input;
712
796
  if (!uuid) {
713
797
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
714
798
  }
799
+ return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
800
+ });
801
+ }
802
+ acquireUnlocked(input) {
803
+ var _a;
804
+ return __awaiter(this, void 0, void 0, function* () {
805
+ const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
715
806
  const cachedTransport = transportCache[uuid];
807
+ if (skipProtocolProbe && !cachedTransport && this.blePlxManager) {
808
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] refresh uncached BLE connection for firmware install:', uuid);
809
+ const manager = this.blePlxManager;
810
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
811
+ yield this.runBestEffortNativeOperation('firmware install reconnect: cancel uncached device connection', () => manager.cancelDeviceConnection(uuid));
812
+ }));
813
+ }
716
814
  if (cachedTransport) {
717
- const cachedProtocol = this.deviceProtocol.get(uuid);
718
- const isCachedDeviceConnected = yield cachedTransport.device.isConnected().catch(() => false);
719
- if (isCachedDeviceConnected &&
720
- cachedProtocol &&
721
- (!expectedProtocol || cachedProtocol === expectedProtocol)) {
722
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
723
- return { uuid, protocolType: cachedProtocol };
815
+ if (skipProtocolProbe) {
816
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] refresh cached BLE connection for firmware install:', uuid);
817
+ const manager = this.blePlxManager;
818
+ yield this.releaseUnlocked(uuid, true);
819
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
820
+ const operations = [];
821
+ if (manager) {
822
+ operations.push(this.runBestEffortNativeOperation('firmware install reconnect: cancel device connection', () => manager.cancelDeviceConnection(uuid)));
823
+ }
824
+ operations.push(this.runBestEffortNativeOperation('firmware install reconnect: device cancel connection', () => cachedTransport.device.cancelConnection()));
825
+ yield Promise.all(operations);
826
+ }));
827
+ }
828
+ else {
829
+ const cachedProtocol = this.deviceProtocol.get(uuid);
830
+ const isCachedDeviceConnected = yield cachedTransport.device
831
+ .isConnected()
832
+ .catch(() => false);
833
+ if (isCachedDeviceConnected &&
834
+ cachedProtocol &&
835
+ (!expectedProtocol || cachedProtocol === expectedProtocol)) {
836
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
837
+ return { uuid, protocolType: cachedProtocol };
838
+ }
839
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
840
+ yield this.releaseUnlocked(uuid, true);
724
841
  }
725
- Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
726
- yield this.release(uuid, true);
727
842
  }
728
843
  let device = null;
729
844
  if (forceCleanRunPromise && this.runPromise) {
730
845
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
731
846
  this.runPromise.reject(error);
732
- this.rejectAllProtocolV2Frames(error);
733
847
  this.runPromise = null;
848
+ this.runPromiseDeviceId = null;
734
849
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
735
850
  }
736
851
  const blePlxManager = yield this.getPlxManager();
@@ -763,14 +878,17 @@ class ReactNativeBleTransport {
763
878
  if (!device) {
764
879
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
765
880
  try {
766
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
881
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
767
882
  }
768
883
  catch (e) {
769
884
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
885
+ if (shouldRethrowBleSetupError(e)) {
886
+ throw e;
887
+ }
770
888
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
771
889
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
772
890
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
773
- device = yield blePlxManager.connectToDevice(uuid);
891
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
774
892
  }
775
893
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
776
894
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -786,23 +904,27 @@ class ReactNativeBleTransport {
786
904
  }
787
905
  if (!(yield device.isConnected())) {
788
906
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
907
+ const disconnectedDevice = device;
789
908
  try {
790
- device = yield device.connect(connectOptions);
909
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
791
910
  }
792
911
  catch (e) {
793
912
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
913
+ if (shouldRethrowBleSetupError(e)) {
914
+ throw e;
915
+ }
794
916
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
795
917
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
796
918
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
797
919
  try {
798
- device = yield device.connect();
920
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
799
921
  }
800
922
  catch (e) {
801
923
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
802
924
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
803
925
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
804
- yield device.cancelConnection();
805
- device = yield device.connect();
926
+ yield disconnectedDevice.cancelConnection();
927
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
806
928
  }
807
929
  }
808
930
  }
@@ -811,44 +933,62 @@ class ReactNativeBleTransport {
811
933
  }
812
934
  }
813
935
  }
814
- device = yield requestAndroidMtu(device);
815
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
936
+ device = yield resolveNegotiatedMtu(device);
937
+ const acquiredDevice = device;
938
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
816
939
  const protocolHint = expectedProtocol
817
940
  ? undefined
818
- : (_a = this.deviceProtocolHints.get(uuid)) !== null && _a !== void 0 ? _a : inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
819
- yield this.release(uuid, true);
941
+ : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
942
+ yield this.releaseUnlocked(uuid, true);
820
943
  if (protocolHint) {
821
944
  this.deviceProtocolHints.set(uuid, protocolHint);
822
945
  }
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
- });
946
+ yield this.installTransportForAcquire(uuid, acquiredDevice, {
947
+ writeCharacteristic,
948
+ notifyCharacteristic,
949
+ });
950
+ try {
951
+ if (skipProtocolProbe) {
952
+ if (!expectedProtocol) {
953
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected BLE protocol');
954
+ }
955
+ if (this.sessionProtocols.get(uuid) !== expectedProtocol) {
956
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint');
957
+ }
958
+ this.deviceProtocol.set(uuid, expectedProtocol);
959
+ this.sessionProtocols.set(uuid, expectedProtocol);
960
+ this.protocolReprobeFailures.delete(uuid);
961
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected without probe', {
962
+ deviceId: uuid,
963
+ protocol: expectedProtocol,
964
+ source: 'firmware-install-reconnect',
965
+ });
966
+ const currentTransport = transportCache[uuid];
967
+ if (!currentTransport) {
968
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
969
+ }
970
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
971
+ return { uuid, protocolType: expectedProtocol };
972
+ }
973
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
974
+ yield this.installTransportForAcquire(uuid, acquiredDevice);
975
+ }));
976
+ const currentTransport = transportCache[uuid];
977
+ if (!currentTransport) {
978
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
979
+ }
980
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
981
+ return { uuid, protocolType };
840
982
  }
841
- else if (reactNative.Platform.OS === 'android') {
842
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
983
+ catch (error) {
984
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceBondError) {
985
+ yield this.disconnectUnlocked(uuid);
986
+ }
987
+ else {
988
+ yield this.releaseUnlocked(uuid, true);
989
+ }
990
+ throw error;
843
991
  }
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
992
  });
853
993
  }
854
994
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
@@ -867,7 +1007,7 @@ class ReactNativeBleTransport {
867
1007
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
868
1008
  return;
869
1009
  }
870
- if (this.deviceProtocol.get(uuid) === 'V2') {
1010
+ if (this.getActiveProtocol(uuid) === 'V2') {
871
1011
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
872
1012
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
873
1013
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -885,7 +1025,7 @@ class ReactNativeBleTransport {
885
1025
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
886
1026
  return;
887
1027
  }
888
- if (this.runPromise) {
1028
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
889
1029
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
890
1030
  if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
891
1031
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -900,13 +1040,11 @@ class ReactNativeBleTransport {
900
1040
  ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
901
1041
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
902
1042
  this.runPromise.reject(notifyError);
903
- this.rejectAllProtocolV2Frames(notifyError);
904
1043
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
905
1044
  return;
906
1045
  }
907
1046
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
908
1047
  this.runPromise.reject(notifyError);
909
- this.rejectAllProtocolV2Frames(notifyError);
910
1048
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
911
1049
  }
912
1050
  return;
@@ -920,7 +1058,7 @@ class ReactNativeBleTransport {
920
1058
  }
921
1059
  try {
922
1060
  const data = buffer.Buffer.from(c.value, 'base64');
923
- const protocol = this.deviceProtocol.get(uuid);
1061
+ const protocol = this.getActiveProtocol(uuid);
924
1062
  if (!protocol) {
925
1063
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
926
1064
  return;
@@ -940,16 +1078,18 @@ class ReactNativeBleTransport {
940
1078
  const value = buffer.Buffer.from(buffer$1);
941
1079
  bufferLength = 0;
942
1080
  buffer$1 = [];
943
- (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1081
+ if (this.runPromiseDeviceId === uuid) {
1082
+ (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1083
+ }
944
1084
  }
945
1085
  }
946
1086
  catch (error) {
947
1087
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
948
1088
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
949
- if (this.deviceProtocol.get(uuid) === 'V2') {
1089
+ if (this.getActiveProtocol(uuid) === 'V2') {
950
1090
  this.rejectProtocolV2Frames(uuid, notifyError);
951
1091
  }
952
- else {
1092
+ else if (this.runPromiseDeviceId === uuid) {
953
1093
  (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
954
1094
  }
955
1095
  }
@@ -957,15 +1097,27 @@ class ReactNativeBleTransport {
957
1097
  return subscription;
958
1098
  }
959
1099
  release(uuid, onclose = false) {
960
- var _a, _b, _c, _d, _e, _f, _g;
961
1100
  return __awaiter(this, void 0, void 0, function* () {
962
- const transport = transportCache[uuid];
1101
+ return this.runLifecycleOperation(uuid, () => this.releaseUnlocked(uuid, onclose));
1102
+ });
1103
+ }
1104
+ releaseUnlocked(uuid, onclose = false) {
1105
+ return __awaiter(this, void 0, void 0, function* () {
963
1106
  yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
964
- if (this.runPromise) {
1107
+ return this.releaseNative(uuid, onclose);
1108
+ });
1109
+ }
1110
+ releaseNative(uuid, onclose = false) {
1111
+ var _a, _b, _c, _d, _e;
1112
+ return __awaiter(this, void 0, void 0, function* () {
1113
+ const transport = transportCache[uuid];
1114
+ const manager = this.blePlxManager;
1115
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
965
1116
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
966
1117
  this.runPromise.reject(error);
967
1118
  this.runPromise = null;
968
- this.rejectAllProtocolV2Frames(error);
1119
+ this.runPromiseDeviceId = null;
1120
+ this.rejectProtocolV2Frames(uuid, error);
969
1121
  }
970
1122
  else {
971
1123
  this.resetProtocolV2Frames(uuid);
@@ -985,31 +1137,37 @@ class ReactNativeBleTransport {
985
1137
  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
1138
  (_d = transport.notifySubscription) === null || _d === void 0 ? void 0 : _d.remove();
987
1139
  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
- }
1140
+ if (transportCache[uuid] === transport) {
1141
+ delete transportCache[uuid];
995
1142
  }
996
- delete transportCache[uuid];
997
1143
  }
1144
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
998
1145
  this.deviceProtocol.delete(uuid);
999
- (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1146
+ this.probingProtocols.delete(uuid);
1147
+ (_e = this.protocolV2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1000
1148
  this.protocolV2Assemblers.delete(uuid);
1001
1149
  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
- }
1150
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1151
+ const operations = [
1152
+ this.runBestEffortNativeOperation('release: restore connection priority', () => this.restoreAndroidConnectionPriority(uuid, transport)),
1153
+ ];
1154
+ if ((transport === null || transport === void 0 ? void 0 : transport.notifyTransactionId) && manager) {
1155
+ operations.push(this.runBestEffortNativeOperation('release: cancel notify transaction', () => manager.cancelTransaction(transport.notifyTransactionId)));
1156
+ }
1157
+ if (manager) {
1158
+ operations.push(this.runBestEffortNativeOperation('release: cancel transaction', () => manager.cancelTransaction(uuid)));
1159
+ }
1160
+ yield Promise.all(operations);
1161
+ }));
1008
1162
  return Promise.resolve(true);
1009
1163
  });
1010
1164
  }
1011
1165
  post(session, name, data) {
1012
1166
  return __awaiter(this, void 0, void 0, function* () {
1167
+ if (this.getProtocolType(session) === 'V2') {
1168
+ yield this.protocolV2Links.sendFlowControl(session, () => this.createProtocolV2Adapter(session), name, data);
1169
+ return;
1170
+ }
1013
1171
  yield this.call(session, name, data);
1014
1172
  });
1015
1173
  }
@@ -1025,7 +1183,6 @@ class ReactNativeBleTransport {
1025
1183
  if (!protocol) {
1026
1184
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1027
1185
  }
1028
- Log === null || Log === void 0 ? void 0 : Log.debug('transport call', createTransportCallLog(name, protocol, data));
1029
1186
  if (protocol === 'V2') {
1030
1187
  return this.callProtocolV2(uuid, name, data, options);
1031
1188
  }
@@ -1044,7 +1201,19 @@ class ReactNativeBleTransport {
1044
1201
  const transport = this.getCachedTransport(uuid);
1045
1202
  const runPromise = hdShared.createDeferred();
1046
1203
  runPromise.promise.catch(() => undefined);
1204
+ const supersededRunPromise = this.runPromise;
1205
+ if (supersededRunPromise) {
1206
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1207
+ }
1047
1208
  this.runPromise = runPromise;
1209
+ this.runPromiseDeviceId = uuid;
1210
+ const releaseOwnershipIfCurrent = () => {
1211
+ if (this.runPromise === runPromise) {
1212
+ this.runPromise = null;
1213
+ this.runPromiseDeviceId = null;
1214
+ }
1215
+ };
1216
+ const isCurrentOwner = () => this.runPromise === runPromise;
1048
1217
  const messages = this._messages;
1049
1218
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1050
1219
  let timeout;
@@ -1065,6 +1234,9 @@ class ReactNativeBleTransport {
1065
1234
  }
1066
1235
  catch (e) {
1067
1236
  onError(e);
1237
+ if (isWedgedWriteError(e)) {
1238
+ throw e;
1239
+ }
1068
1240
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1069
1241
  }
1070
1242
  }
@@ -1092,6 +1264,9 @@ class ReactNativeBleTransport {
1092
1264
  }
1093
1265
  catch (e) {
1094
1266
  onError(e);
1267
+ if (isWedgedWriteError(e)) {
1268
+ throw e;
1269
+ }
1095
1270
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1096
1271
  }
1097
1272
  }
@@ -1102,8 +1277,8 @@ class ReactNativeBleTransport {
1102
1277
  });
1103
1278
  }
1104
1279
  if (name === 'EmmcFileWrite') {
1105
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1106
- this.runPromise = null;
1280
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1281
+ releaseOwnershipIfCurrent();
1107
1282
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1108
1283
  });
1109
1284
  }
@@ -1119,7 +1294,7 @@ class ReactNativeBleTransport {
1119
1294
  let attempt = 0;
1120
1295
  while (true) {
1121
1296
  try {
1122
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1297
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1123
1298
  return;
1124
1299
  }
1125
1300
  catch (error) {
@@ -1127,36 +1302,18 @@ class ReactNativeBleTransport {
1127
1302
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1128
1303
  throw error;
1129
1304
  }
1130
- const shouldReconnect = retryType === 'reconnectable';
1131
- const delayMs = shouldReconnect
1132
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1133
- : resolveFirmwareUploadRetryDelay(attempt);
1305
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1134
1306
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1135
1307
  attempt: attempt + 1,
1136
1308
  delayMs,
1137
- reconnect: shouldReconnect,
1138
1309
  error,
1139
1310
  });
1140
- if (shouldReconnect) {
1141
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1142
- }
1143
1311
  yield delay(delayMs);
1144
1312
  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
1313
  }
1157
1314
  }
1158
1315
  }), e => {
1159
- this.runPromise = null;
1316
+ releaseOwnershipIfCurrent();
1160
1317
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1161
1318
  });
1162
1319
  }
@@ -1164,11 +1321,17 @@ class ReactNativeBleTransport {
1164
1321
  for (const o of buffers) {
1165
1322
  const outData = o.toString('base64');
1166
1323
  try {
1167
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1324
+ const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1325
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1326
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1327
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1168
1328
  }
1169
1329
  catch (e) {
1170
1330
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1171
- this.runPromise = null;
1331
+ releaseOwnershipIfCurrent();
1332
+ if (isWedgedWriteError(e)) {
1333
+ throw e;
1334
+ }
1172
1335
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1173
1336
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1174
1337
  }
@@ -1201,12 +1364,19 @@ class ReactNativeBleTransport {
1201
1364
  return check.call(jsonData);
1202
1365
  }
1203
1366
  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);
1367
+ if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1368
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1206
1369
  }
1207
1370
  else {
1208
1371
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1209
1372
  }
1373
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1374
+ const isStaleCall = this.runPromise !== runPromise;
1375
+ if (!isProbeTimeout &&
1376
+ !isStaleCall &&
1377
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1378
+ yield this.disconnect(uuid);
1379
+ }
1210
1380
  throw e;
1211
1381
  }
1212
1382
  finally {
@@ -1214,6 +1384,7 @@ class ReactNativeBleTransport {
1214
1384
  clearTimeout(timeout);
1215
1385
  if (this.runPromise === runPromise) {
1216
1386
  this.runPromise = null;
1387
+ this.runPromiseDeviceId = null;
1217
1388
  }
1218
1389
  }
1219
1390
  });
@@ -1222,10 +1393,17 @@ class ReactNativeBleTransport {
1222
1393
  this.stopped = true;
1223
1394
  }
1224
1395
  disconnect(session) {
1225
- var _a, _b, _c, _d, _e;
1396
+ return __awaiter(this, void 0, void 0, function* () {
1397
+ return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
1398
+ });
1399
+ }
1400
+ disconnectUnlocked(session) {
1401
+ var _a, _b, _c;
1226
1402
  return __awaiter(this, void 0, void 0, function* () {
1227
1403
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1228
1404
  const transport = transportCache[session];
1405
+ const manager = this.blePlxManager;
1406
+ const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
1229
1407
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1230
1408
  try {
1231
1409
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1238,7 +1416,7 @@ class ReactNativeBleTransport {
1238
1416
  }
1239
1417
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1240
1418
  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);
1419
+ 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
1420
  transport.notifySubscription.remove();
1243
1421
  transport.notifySubscription = undefined;
1244
1422
  }
@@ -1246,52 +1424,201 @@ class ReactNativeBleTransport {
1246
1424
  Log === null || Log === void 0 ? void 0 : Log.error('disconnect: remove notify subscription error: ', e);
1247
1425
  }
1248
1426
  }
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]) {
1427
+ if (!transport || transportCache[session] === transport) {
1272
1428
  delete transportCache[session];
1273
1429
  }
1274
1430
  this.deviceProtocol.delete(session);
1431
+ this.probingProtocols.delete(session);
1275
1432
  this.deviceProtocolHints.delete(session);
1433
+ this.sessionProtocols.delete(session);
1434
+ this.protocolReprobeFailures.delete(session);
1276
1435
  this.protocolV2Assemblers.delete(session);
1277
1436
  this.resetProtocolV2Frames(session);
1278
1437
  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
- });
1438
+ this.emitDeviceDisconnect(session, (_c = transport === null || transport === void 0 ? void 0 : transport.device) === null || _c === void 0 ? void 0 : _c.name, monitorToken);
1284
1439
  }
1285
1440
  catch (e) {
1286
1441
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1287
1442
  }
1443
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1444
+ this.monitorTokens.delete(session);
1445
+ }
1446
+ yield this.runNativeTeardown(session, manager, () => __awaiter(this, void 0, void 0, function* () {
1447
+ const operations = [];
1448
+ if (manager) {
1449
+ operations.push(this.runBestEffortNativeOperation('disconnect: cancel transaction', () => manager.cancelTransaction(session)));
1450
+ operations.push(this.runBestEffortNativeOperation('disconnect: cancel device connection', () => manager.cancelDeviceConnection(session)));
1451
+ }
1452
+ if (transport === null || transport === void 0 ? void 0 : transport.device) {
1453
+ operations.push(this.runBestEffortNativeOperation('disconnect: device cancel connection', () => transport.device.cancelConnection()));
1454
+ }
1455
+ yield Promise.all(operations);
1456
+ }));
1288
1457
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1289
1458
  });
1290
1459
  }
1460
+ runNativeTeardown(uuid, manager, teardown) {
1461
+ return __awaiter(this, void 0, void 0, function* () {
1462
+ let timer;
1463
+ let timedOut = false;
1464
+ const pending = Promise.resolve()
1465
+ .then(teardown)
1466
+ .catch(error => {
1467
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE native teardown error (ignored): ', (error === null || error === void 0 ? void 0 : error.message) || error);
1468
+ });
1469
+ try {
1470
+ yield Promise.race([
1471
+ pending,
1472
+ new Promise(resolve => {
1473
+ timer = setTimeout(() => {
1474
+ timedOut = true;
1475
+ resolve();
1476
+ }, BLE_NATIVE_TEARDOWN_TIMEOUT_MS);
1477
+ }),
1478
+ ]);
1479
+ }
1480
+ finally {
1481
+ if (timer)
1482
+ clearTimeout(timer);
1483
+ }
1484
+ if (timedOut) {
1485
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE native teardown timed out:', uuid);
1486
+ if (this.blePlxManager === manager) {
1487
+ this.resetPlxManager();
1488
+ }
1489
+ }
1490
+ });
1491
+ }
1492
+ runBestEffortNativeOperation(label, operation) {
1493
+ return Promise.resolve()
1494
+ .then(operation)
1495
+ .catch(error => {
1496
+ Log === null || Log === void 0 ? void 0 : Log.debug(`${label} error (ignored): `, (error === null || error === void 0 ? void 0 : error.message) || error);
1497
+ });
1498
+ }
1499
+ runLifecycleOperation(uuid, operation) {
1500
+ var _a;
1501
+ return __awaiter(this, void 0, void 0, function* () {
1502
+ const previousOperation = (_a = this.lifecycleOperations.get(uuid)) !== null && _a !== void 0 ? _a : Promise.resolve();
1503
+ let completeOperation;
1504
+ const operationGate = new Promise(resolve => {
1505
+ completeOperation = resolve;
1506
+ });
1507
+ const operationTail = previousOperation.catch(() => undefined).then(() => operationGate);
1508
+ this.lifecycleOperations.set(uuid, operationTail);
1509
+ yield previousOperation.catch(() => undefined);
1510
+ try {
1511
+ return yield operation();
1512
+ }
1513
+ finally {
1514
+ completeOperation();
1515
+ if (this.lifecycleOperations.get(uuid) === operationTail) {
1516
+ this.lifecycleOperations.delete(uuid);
1517
+ }
1518
+ }
1519
+ });
1520
+ }
1291
1521
  cancel() {
1292
1522
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1293
1523
  if (this.runPromise) ;
1294
1524
  this.runPromise = null;
1525
+ this.runPromiseDeviceId = null;
1526
+ }
1527
+ connectWithTimeout(uuid, connect) {
1528
+ return __awaiter(this, void 0, void 0, function* () {
1529
+ let timer;
1530
+ let timedOut = false;
1531
+ const pending = connect();
1532
+ pending.catch(() => undefined);
1533
+ try {
1534
+ const result = yield Promise.race([
1535
+ pending,
1536
+ new Promise((_, reject) => {
1537
+ timer = setTimeout(() => {
1538
+ timedOut = true;
1539
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1540
+ }, BLE_CONNECT_TIMEOUT_MS);
1541
+ }),
1542
+ ]);
1543
+ return result;
1544
+ }
1545
+ catch (error) {
1546
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1547
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1548
+ if (resetManager) {
1549
+ throw this.createWedgedBleSetupError();
1550
+ }
1551
+ }
1552
+ throw error;
1553
+ }
1554
+ finally {
1555
+ if (timer)
1556
+ clearTimeout(timer);
1557
+ }
1558
+ });
1559
+ }
1560
+ resolveCharacteristicsWithTimeout(uuid, device) {
1561
+ return __awaiter(this, void 0, void 0, function* () {
1562
+ let timer;
1563
+ let timedOut = false;
1564
+ const pending = this.resolveCharacteristics(device);
1565
+ pending.catch(() => undefined);
1566
+ try {
1567
+ const result = yield Promise.race([
1568
+ pending,
1569
+ new Promise((_, reject) => {
1570
+ timer = setTimeout(() => {
1571
+ timedOut = true;
1572
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1573
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1574
+ }),
1575
+ ]);
1576
+ this.connectionSetupTimeoutCounts.delete(uuid);
1577
+ return result;
1578
+ }
1579
+ catch (error) {
1580
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1581
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1582
+ if (resetManager) {
1583
+ throw this.createWedgedBleSetupError();
1584
+ }
1585
+ }
1586
+ throw error;
1587
+ }
1588
+ finally {
1589
+ if (timer)
1590
+ clearTimeout(timer);
1591
+ }
1592
+ });
1593
+ }
1594
+ abandonStalledConnection(uuid, stage) {
1595
+ var _a, _b;
1596
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1597
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1598
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1599
+ stage,
1600
+ setupTimeoutsSinceSuccess: timeouts,
1601
+ });
1602
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1603
+ });
1604
+ const stalled = transportCache[uuid];
1605
+ if (stalled) {
1606
+ delete transportCache[uuid];
1607
+ }
1608
+ this.deviceProtocol.delete(uuid);
1609
+ this.probingProtocols.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,49 +1627,183 @@ 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.protocolV2Assemblers.delete(uuid);
1680
+ this.resetProtocolV2Frames(uuid);
1681
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1682
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1683
+ this.resetPlxManager();
1684
+ this.writeTimeoutCounts.delete(uuid);
1685
+ }
1686
+ }
1687
+ resetPlxManager() {
1688
+ const manager = this.blePlxManager;
1689
+ this.blePlxManager = undefined;
1690
+ const reason = 'React Native BLE manager reset';
1691
+ Object.entries(transportCache).forEach(([uuid, cachedTransport]) => {
1692
+ var _a, _b, _c, _d;
1693
+ try {
1694
+ (_a = cachedTransport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
1695
+ }
1696
+ catch (error) {
1697
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect subscription removal failed:', error);
1698
+ }
1699
+ cachedTransport.disconnectSubscription = undefined;
1700
+ try {
1701
+ (_b = cachedTransport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
1702
+ }
1703
+ catch (error) {
1704
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset notify subscription removal failed:', error);
1705
+ }
1706
+ cachedTransport.notifySubscription = undefined;
1707
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
1708
+ try {
1709
+ 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));
1710
+ }
1711
+ catch (error) {
1712
+ Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect event failed:', error);
1713
+ }
1714
+ delete transportCache[uuid];
1715
+ });
1716
+ this.protocolV2Links.invalidateAllLinks(reason).catch(error => {
1717
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager link invalidation failed:', error);
1718
+ });
1719
+ this.deviceProtocol.clear();
1720
+ this.probingProtocols.clear();
1721
+ this.sessionProtocols.clear();
1722
+ this.confirmedProtocolV2.clear();
1723
+ this.protocolReprobeFailures.clear();
1724
+ this.writeTimeoutCounts.clear();
1725
+ this.connectionSetupTimeoutCounts.clear();
1726
+ this.monitorTokens.clear();
1727
+ this.protocolV2Assemblers.clear();
1728
+ try {
1729
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1730
+ }
1731
+ catch (error) {
1732
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1733
+ }
1734
+ }
1735
+ createProtocolMismatchError(expected, uuid) {
1736
+ const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1737
+ 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
1738
  }
1306
1739
  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');
1740
+ 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
1741
  }
1309
1742
  clearProbeProtocol(uuid, protocol) {
1743
+ if (this.probingProtocols.get(uuid) === protocol) {
1744
+ this.probingProtocols.delete(uuid);
1745
+ }
1310
1746
  if (this.deviceProtocol.get(uuid) === protocol) {
1311
1747
  this.deviceProtocol.delete(uuid);
1312
1748
  }
1313
1749
  }
1314
- detectProtocol(uuid, expectedProtocol, protocolHint) {
1750
+ getActiveProtocol(uuid) {
1751
+ var _a;
1752
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1753
+ }
1754
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1755
+ var _a;
1315
1756
  return __awaiter(this, void 0, void 0, function* () {
1316
1757
  if (expectedProtocol === 'V1') {
1317
- if (yield this.probeProtocolV1(uuid)) {
1318
- this.deviceProtocol.set(uuid, 'V1');
1758
+ this.deviceProtocol.set(uuid, 'V1');
1759
+ this.sessionProtocols.set(uuid, 'V1');
1760
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1761
+ deviceId: uuid,
1762
+ protocol: 'V1',
1763
+ source: 'expected',
1764
+ });
1765
+ return 'V1';
1766
+ }
1767
+ if (expectedProtocol === 'V2') {
1768
+ if (yield this.probeProtocolV2(uuid)) {
1769
+ this.deviceProtocol.set(uuid, 'V2');
1770
+ this.sessionProtocols.set(uuid, 'V2');
1771
+ this.confirmedProtocolV2.add(uuid);
1319
1772
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1320
1773
  deviceId: uuid,
1321
- protocol: 'V1',
1774
+ protocol: 'V2',
1322
1775
  source: 'expected',
1323
1776
  });
1324
- return 'V1';
1777
+ return 'V2';
1325
1778
  }
1326
- throw this.createProtocolMismatchError(expectedProtocol);
1327
- }
1328
- 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'];
1779
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
1780
+ }
1781
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1782
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1783
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1784
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1785
+ !protocolHint &&
1786
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1787
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1338
1788
  for (let i = 0; i < probeOrder.length; i += 1) {
1339
1789
  const protocol = probeOrder[i];
1340
1790
  if (i > 0) {
1341
1791
  yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1792
+ if (!transportCache[uuid]) {
1793
+ if (!rebuildTransport) {
1794
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1795
+ }
1796
+ yield rebuildTransport();
1797
+ }
1342
1798
  }
1343
1799
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1344
1800
  if (detected) {
1345
1801
  this.deviceProtocol.set(uuid, protocol);
1802
+ this.sessionProtocols.set(uuid, protocol);
1803
+ if (protocol === 'V2') {
1804
+ this.confirmedProtocolV2.add(uuid);
1805
+ }
1806
+ this.protocolReprobeFailures.delete(uuid);
1346
1807
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1347
1808
  deviceId: uuid,
1348
1809
  protocol,
@@ -1351,7 +1812,14 @@ class ReactNativeBleTransport {
1351
1812
  return protocol;
1352
1813
  }
1353
1814
  }
1815
+ if (trustSessionProtocol) {
1816
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1817
+ }
1818
+ else {
1819
+ this.protocolReprobeFailures.delete(uuid);
1820
+ }
1354
1821
  this.deviceProtocol.delete(uuid);
1822
+ this.probingProtocols.delete(uuid);
1355
1823
  throw this.createProtocolDetectionError();
1356
1824
  });
1357
1825
  }
@@ -1403,13 +1871,17 @@ class ReactNativeBleTransport {
1403
1871
  return false;
1404
1872
  }
1405
1873
  try {
1406
- this.deviceProtocol.set(uuid, 'V1');
1407
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1874
+ this.probingProtocols.set(uuid, 'V1');
1875
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1876
+ this.probingProtocols.delete(uuid);
1408
1877
  return true;
1409
1878
  }
1410
1879
  catch (error) {
1411
1880
  this.clearProbeProtocol(uuid, 'V1');
1412
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1881
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1882
+ if (isWedgedWriteError(error)) {
1883
+ throw error;
1884
+ }
1413
1885
  return false;
1414
1886
  }
1415
1887
  });
@@ -1420,7 +1892,7 @@ class ReactNativeBleTransport {
1420
1892
  if (!this._messages || !this._messagesV2) {
1421
1893
  return false;
1422
1894
  }
1423
- this.deviceProtocol.set(uuid, 'V2');
1895
+ this.probingProtocols.set(uuid, 'V2');
1424
1896
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1425
1897
  const detected = yield transport.probeProtocolV2({
1426
1898
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1436,6 +1908,9 @@ class ReactNativeBleTransport {
1436
1908
  if (!detected) {
1437
1909
  this.clearProbeProtocol(uuid, 'V2');
1438
1910
  }
1911
+ else {
1912
+ this.probingProtocols.delete(uuid);
1913
+ }
1439
1914
  return detected;
1440
1915
  });
1441
1916
  }
@@ -1478,16 +1953,8 @@ class ReactNativeBleTransport {
1478
1953
  }
1479
1954
  this.getProtocolV2FrameQueue(uuid).push(frame);
1480
1955
  }
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
1956
  resetProtocolV2Frames(uuid) {
1489
- this.protocolV2FrameQueues.delete(uuid);
1490
- this.protocolV2FramePromises.delete(uuid);
1957
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1491
1958
  }
1492
1959
  rejectProtocolV2Frames(uuid, error) {
1493
1960
  this.protocolV2FrameQueues.delete(uuid);
@@ -1515,20 +1982,70 @@ class ReactNativeBleTransport {
1515
1982
  }
1516
1983
  });
1517
1984
  }
1518
- writeProtocolV2Frame(transport, frame) {
1985
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1986
+ return __awaiter(this, void 0, void 0, function* () {
1987
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
1988
+ platform: reactNative.Platform.OS,
1989
+ highThroughput: context.highThroughput,
1990
+ requestedWithResponse: context.writeWithResponse,
1991
+ characteristic: transport.writeCharacteristic,
1992
+ });
1993
+ let attempt = 0;
1994
+ for (;;) {
1995
+ assertCurrentGeneration();
1996
+ if (context.signal.aborted) {
1997
+ throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1998
+ }
1999
+ try {
2000
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
2001
+ ? transport.writeCharacteristic.writeWithResponse(payload)
2002
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
2003
+ try {
2004
+ assertCurrentGeneration();
2005
+ return !context.signal.aborted;
2006
+ }
2007
+ catch (_a) {
2008
+ return false;
2009
+ }
2010
+ });
2011
+ assertCurrentGeneration();
2012
+ return;
2013
+ }
2014
+ catch (error) {
2015
+ if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2016
+ attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2017
+ throw error;
2018
+ }
2019
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
2020
+ attempt += 1;
2021
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
2022
+ name: context.messageName,
2023
+ attempt,
2024
+ delayMs,
2025
+ });
2026
+ yield delay(delayMs);
2027
+ }
2028
+ }
2029
+ });
2030
+ }
2031
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1519
2032
  return __awaiter(this, void 0, void 0, function* () {
1520
2033
  const tuning = getProtocolV2BleTuning();
1521
2034
  const packetCapacity = resolveProtocolV2PacketCapacity({
1522
2035
  platform: reactNative.Platform.OS,
1523
2036
  iosPacketLength: tuning.iosPacketLength,
1524
2037
  androidPacketLength: tuning.androidPacketLength,
1525
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
2038
+ mtu: transport$1.mtuSize,
2039
+ });
2040
+ yield transport.writeProtocolV2BleFrame({
2041
+ frame,
2042
+ packetCapacity,
2043
+ assertActive: assertCurrentGeneration,
2044
+ signal: context.signal,
2045
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
2046
+ wait: delay,
2047
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1526
2048
  });
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
2049
  });
1533
2050
  }
1534
2051
  callProtocolV2(uuid, name, data, options) {
@@ -1537,15 +2054,40 @@ class ReactNativeBleTransport {
1537
2054
  if (!this._messages || !this._messagesV2) {
1538
2055
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1539
2056
  }
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) {
2057
+ const callOptions = options;
2058
+ const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
2059
+ if (highThroughputWrite) {
2060
+ yield this.ensureProtocolV2HighThroughputMtu(uuid);
1543
2061
  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,
2062
+ const currentTransport = this.getCachedTransport(uuid);
2063
+ const writeWithResponse = shouldWriteProtocolV2WithResponse({
2064
+ platform: reactNative.Platform.OS,
2065
+ highThroughput: true,
2066
+ requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
2067
+ characteristic: currentTransport.writeCharacteristic,
2068
+ });
2069
+ const packetCapacity = resolveProtocolV2PacketCapacity({
2070
+ platform: reactNative.Platform.OS,
2071
+ iosPacketLength: tuning.iosPacketLength,
2072
+ androidPacketLength: tuning.androidPacketLength,
2073
+ mtu: currentTransport.mtuSize,
1548
2074
  });
2075
+ const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
2076
+ const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
2077
+ const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
2078
+ if (!loggedSignatures.has(logSignature)) {
2079
+ loggedSignatures.add(logSignature);
2080
+ this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
2081
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
2082
+ name,
2083
+ writeMode,
2084
+ reportedMtu: currentTransport.mtuSize,
2085
+ packetCapacity,
2086
+ });
2087
+ }
2088
+ }
2089
+ if (highThroughputWrite) {
2090
+ yield this.enableAndroidHighConnectionPriority(uuid);
1549
2091
  }
1550
2092
  try {
1551
2093
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
@@ -1554,6 +2096,85 @@ class ReactNativeBleTransport {
1554
2096
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1555
2097
  throw e;
1556
2098
  }
2099
+ finally {
2100
+ if (highThroughputWrite) {
2101
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
2102
+ }
2103
+ }
2104
+ });
2105
+ }
2106
+ ensureProtocolV2HighThroughputMtu(uuid) {
2107
+ return __awaiter(this, void 0, void 0, function* () {
2108
+ const transport = this.getCachedTransport(uuid);
2109
+ if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
2110
+ return;
2111
+ const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
2112
+ transport.device = refreshedDevice;
2113
+ transport.mtuSize =
2114
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
2115
+ if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
2116
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`);
2117
+ }
2118
+ });
2119
+ }
2120
+ clearAndroidPriorityResetTimer(uuid) {
2121
+ const timerId = this.androidPriorityResetTimers.get(uuid);
2122
+ if (timerId !== undefined) {
2123
+ clearTimeout(timerId);
2124
+ this.androidPriorityResetTimers.delete(uuid);
2125
+ }
2126
+ }
2127
+ enableAndroidHighConnectionPriority(uuid) {
2128
+ return __awaiter(this, void 0, void 0, function* () {
2129
+ if (reactNative.Platform.OS !== 'android')
2130
+ return;
2131
+ this.clearAndroidPriorityResetTimer(uuid);
2132
+ if (this.androidHighPriorityDevices.has(uuid))
2133
+ return;
2134
+ const transport = transportCache[uuid];
2135
+ if (!transport)
2136
+ return;
2137
+ try {
2138
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
2139
+ this.androidHighPriorityDevices.add(uuid);
2140
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2141
+ priority: 'high',
2142
+ });
2143
+ }
2144
+ catch (error) {
2145
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
2146
+ error: error instanceof Error ? error.message : String(error),
2147
+ });
2148
+ }
2149
+ });
2150
+ }
2151
+ scheduleAndroidBalancedConnectionPriority(uuid) {
2152
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
2153
+ return;
2154
+ this.clearAndroidPriorityResetTimer(uuid);
2155
+ const timerId = setTimeout(() => {
2156
+ this.androidPriorityResetTimers.delete(uuid);
2157
+ this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
2158
+ }, ANDROID_HIGH_PRIORITY_IDLE_MS);
2159
+ this.androidPriorityResetTimers.set(uuid, timerId);
2160
+ }
2161
+ restoreAndroidConnectionPriority(uuid, transport) {
2162
+ return __awaiter(this, void 0, void 0, function* () {
2163
+ this.clearAndroidPriorityResetTimer(uuid);
2164
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
2165
+ return;
2166
+ }
2167
+ try {
2168
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
2169
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2170
+ priority: 'balanced',
2171
+ });
2172
+ }
2173
+ catch (error) {
2174
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
2175
+ error: error instanceof Error ? error.message : String(error),
2176
+ });
2177
+ }
1557
2178
  });
1558
2179
  }
1559
2180
  createProtocolV2Adapter(uuid) {
@@ -1574,10 +2195,10 @@ class ReactNativeBleTransport {
1574
2195
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1575
2196
  this.resetProtocolV2Frames(uuid);
1576
2197
  },
1577
- writeFrame: (frame) => __awaiter(this, void 0, void 0, function* () {
2198
+ writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1578
2199
  assertCurrentGeneration();
1579
2200
  const currentTransport = this.getCachedTransport(uuid);
1580
- yield this.writeProtocolV2Frame(currentTransport, frame);
2201
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1581
2202
  }),
1582
2203
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1583
2204
  assertCurrentGeneration();
@@ -1589,6 +2210,8 @@ class ReactNativeBleTransport {
1589
2210
  }),
1590
2211
  reset: (reason) => {
1591
2212
  var _a;
2213
+ if (this.monitorTokens.get(uuid) !== generation)
2214
+ return;
1592
2215
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1593
2216
  this.rejectProtocolV2Frames(uuid, new Error(reason));
1594
2217
  },
@@ -1598,11 +2221,20 @@ class ReactNativeBleTransport {
1598
2221
  };
1599
2222
  }
1600
2223
  getProtocolType(path) {
1601
- return this.deviceProtocol.get(path);
2224
+ return this.getActiveProtocol(path);
1602
2225
  }
1603
2226
  }
1604
2227
 
2228
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2229
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2230
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2231
+ exports.BLE_NATIVE_TEARDOWN_TIMEOUT_MS = BLE_NATIVE_TEARDOWN_TIMEOUT_MS;
2232
+ exports.BLE_SETUP_WEDGED_MESSAGE = BLE_SETUP_WEDGED_MESSAGE;
2233
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2234
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2235
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1605
2236
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1606
2237
  exports["default"] = ReactNativeBleTransport;
2238
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1607
2239
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1608
2240
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;