@onekeyfe/hd-transport-react-native 1.2.0-alpha.9 → 1.2.0-alpha.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,51 +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 resolveBleWriteMode(characteristic, preferredMode = 'withoutResponse') {
142
- if (preferredMode === 'withoutResponse' && characteristic.isWritableWithoutResponse) {
143
- return 'withoutResponse';
144
- }
145
- if (preferredMode === 'withResponse' && characteristic.isWritableWithResponse) {
146
- return 'withResponse';
147
- }
148
- if (characteristic.isWritableWithoutResponse) {
149
- return 'withoutResponse';
150
- }
151
- if (characteristic.isWritableWithResponse) {
152
- return 'withResponse';
153
- }
154
- return preferredMode;
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);
155
143
  }
156
- function resolveProtocolV2PacketCapacity({ platform, iosPacketLength = IOS_PACKET_LENGTH, androidPacketLength = ANDROID_PACKET_LENGTH, mtu, }) {
157
- if (platform === 'ios') {
158
- return iosPacketLength;
159
- }
160
- if (platform === 'android') {
161
- const payloadLength = Math.max((mtu !== null && mtu !== void 0 ? mtu : ANDROID_DEFAULT_MTU) - 3, 1);
162
- return Math.min(androidPacketLength, payloadLength);
163
- }
164
- return androidPacketLength;
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);
165
153
  }
166
154
 
167
155
  const timer = process.env.NODE_ENV === 'development'
@@ -189,7 +177,6 @@ const timer = process.env.NODE_ENV === 'development'
189
177
  const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject) => {
190
178
  let done = false;
191
179
  const subscription = bleManager.onStateChange(state => {
192
- console.log('ble state -> ', state);
193
180
  if (state === 'PoweredOn') {
194
181
  if (done)
195
182
  return;
@@ -219,47 +206,20 @@ const isHeaderChunk = (chunk) => {
219
206
  return false;
220
207
  };
221
208
 
222
- const Log$1 = bleLogger;
223
209
  class BleTransport {
224
210
  constructor(device, writeCharacteristic, notifyCharacteristic) {
225
211
  this.name = 'ReactNativeBleTransport';
226
- this.mtuSize = 23;
227
212
  this.id = device.id;
228
213
  this.device = device;
229
214
  this.writeCharacteristic = writeCharacteristic;
230
215
  this.notifyCharacteristic = notifyCharacteristic;
231
216
  }
232
- writeWithRetry(data, retryCount = BleTransport.MAX_RETRIES) {
217
+ writeWithRetry(data) {
233
218
  return __awaiter(this, void 0, void 0, function* () {
234
- try {
235
- yield this.writeCharacteristic.writeWithoutResponse(data);
236
- }
237
- catch (error) {
238
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`Write retry attempt ${BleTransport.MAX_RETRIES - retryCount + 1}, error: ${error}`);
239
- if (retryCount > 0) {
240
- yield hdShared.wait(BleTransport.RETRY_DELAY);
241
- if (error.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
242
- error.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
243
- try {
244
- yield this.device.connect();
245
- yield this.device.discoverAllServicesAndCharacteristics();
246
- }
247
- catch (e) {
248
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`Connect or discoverAllServicesAndCharacteristics error: ${e}`);
249
- }
250
- }
251
- else {
252
- Log$1 === null || Log$1 === void 0 ? void 0 : Log$1.debug(`writeCharacteristic error: ${error}`);
253
- }
254
- return this.writeWithRetry(data, retryCount - 1);
255
- }
256
- throw error;
257
- }
219
+ yield this.writeCharacteristic.writeWithoutResponse(data);
258
220
  });
259
221
  }
260
222
  }
261
- BleTransport.MAX_RETRIES = 5;
262
- BleTransport.RETRY_DELAY = 2000;
263
223
 
264
224
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
265
225
  const Log = bleLogger;
@@ -268,10 +228,35 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
268
228
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
269
229
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
270
230
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
271
- const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
272
231
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
273
232
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
274
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
+ };
275
260
  const delay = (ms) => new Promise(resolve => {
276
261
  setTimeout(resolve, ms);
277
262
  });
@@ -279,10 +264,6 @@ const getFirmwareUploadWriteRetryType = (error) => {
279
264
  if (!error || typeof error !== 'object')
280
265
  return null;
281
266
  const bleWriteError = error;
282
- if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
283
- bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
284
- return 'reconnectable';
285
- }
286
267
  if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
287
268
  bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
288
269
  return 'congested';
@@ -290,25 +271,23 @@ const getFirmwareUploadWriteRetryType = (error) => {
290
271
  const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
291
272
  .filter(value => typeof value === 'string')
292
273
  .join(' ');
293
- return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
274
+ return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
294
275
  };
295
276
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
296
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
297
277
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
298
278
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
299
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
279
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
280
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
281
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
282
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
283
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
284
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
285
+ const DEVICE_SCAN_TIMEOUT_MS = 3000;
300
286
  const IOS_NOTIFY_READY_DELAY_MS = 150;
301
287
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
302
- const HIGH_VOLUME_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 6;
303
- const HIGH_VOLUME_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 6 : 2;
304
- const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 20 : 8;
305
288
  const DEFAULT_PROTOCOL_V2_BLE_TUNING = {
306
- iosPacketLength: IOS_PACKET_LENGTH,
307
- androidPacketLength: ANDROID_PACKET_LENGTH,
308
- highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
309
- highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
310
- highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
311
- highVolumeWriteWithResponse: false,
289
+ iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
290
+ androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
312
291
  };
313
292
  let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
314
293
  const normalizePositiveInteger = (value, fallback) => {
@@ -318,20 +297,15 @@ const normalizePositiveInteger = (value, fallback) => {
318
297
  return Math.floor(normalized);
319
298
  };
320
299
  function configureProtocolV2BleTuning(tuning = {}) {
321
- var _a;
322
300
  protocolV2BleTuning = {
323
301
  iosPacketLength: normalizePositiveInteger(tuning.iosPacketLength, protocolV2BleTuning.iosPacketLength),
324
302
  androidPacketLength: normalizePositiveInteger(tuning.androidPacketLength, protocolV2BleTuning.androidPacketLength),
325
- highVolumeWriteBurstSize: normalizePositiveInteger(tuning.highVolumeWriteBurstSize, protocolV2BleTuning.highVolumeWriteBurstSize),
326
- highVolumeWritePauseMs: normalizePositiveInteger(tuning.highVolumeWritePauseMs, protocolV2BleTuning.highVolumeWritePauseMs),
327
- highVolumeWriteFlushDelayMs: normalizePositiveInteger(tuning.highVolumeWriteFlushDelayMs, protocolV2BleTuning.highVolumeWriteFlushDelayMs),
328
- highVolumeWriteWithResponse: (_a = tuning.highVolumeWriteWithResponse) !== null && _a !== void 0 ? _a : protocolV2BleTuning.highVolumeWriteWithResponse,
329
303
  };
330
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
304
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
331
305
  }
332
306
  function resetProtocolV2BleTuning() {
333
307
  protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
334
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
308
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
335
309
  }
336
310
  function getProtocolV2BleTuning() {
337
311
  return Object.assign({}, protocolV2BleTuning);
@@ -342,19 +316,29 @@ function inferProtocolHintFromDeviceName(name) {
342
316
  function getDeviceDisplayName(device) {
343
317
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
344
318
  }
345
- function isGenericBleService(uuid) {
346
- return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
347
- }
348
- function hasKnownOneKeyService(device) {
349
- var _a;
350
- return ((_a = device === null || device === void 0 ? void 0 : device.serviceUUIDs) !== null && _a !== void 0 ? _a : []).some(serviceUuid => getInfosForServiceUuid(serviceUuid, 'classic'));
351
- }
352
- const ANDROID_REQUEST_MTU = 256;
319
+ const IOS_REQUEST_MTU = 247;
320
+ const ANDROID_REQUEST_MTU = 517;
321
+ const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
322
+ const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
323
+ const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
324
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
353
325
  const connectOptions = {
354
- requestMTU: ANDROID_REQUEST_MTU,
355
- timeout: 3000,
326
+ requestMTU: getRequestedBleMtu(),
327
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
356
328
  refreshGatt: 'OnConnected',
357
329
  };
330
+ const fallbackConnectOptions = {
331
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
332
+ };
333
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
334
+ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
335
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
336
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
337
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
338
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
339
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
340
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
341
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
358
342
  const tryToGetConfiguration = (device) => {
359
343
  if (!device || !device.serviceUUIDs)
360
344
  return null;
@@ -366,22 +350,25 @@ const tryToGetConfiguration = (device) => {
366
350
  return null;
367
351
  return infos;
368
352
  };
369
- const requestAndroidMtu = (device) => __awaiter(void 0, void 0, void 0, function* () {
370
- if (reactNative.Platform.OS !== 'android')
353
+ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
354
+ if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
371
355
  return device;
372
356
  try {
373
- const mtuDevice = yield device.requestMTU(ANDROID_REQUEST_MTU);
374
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android MTU requested:', {
375
- requested: ANDROID_REQUEST_MTU,
376
- mtu: mtuDevice.mtu,
377
- });
357
+ const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
378
358
  return mtuDevice;
379
359
  }
380
360
  catch (error) {
381
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
361
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
362
+ platform: reactNative.Platform.OS,
363
+ stage,
364
+ attempt,
365
+ actual: device.mtu,
366
+ error: error instanceof Error ? error.message : String(error),
367
+ });
382
368
  return device;
383
369
  }
384
370
  });
371
+ const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
385
372
  function remapError(error) {
386
373
  var _a;
387
374
  if (error instanceof reactNativeBlePlx.BleError) {
@@ -408,9 +395,15 @@ class ReactNativeBleTransport {
408
395
  this.stopped = false;
409
396
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
410
397
  this.runPromise = null;
398
+ this.runPromiseDeviceId = null;
411
399
  this.firmwareUploadWriteRecoveryIds = new Set();
412
400
  this.deviceProtocol = new Map();
401
+ this.probingProtocols = new Map();
402
+ this.writeTimeoutCounts = new Map();
403
+ this.connectionSetupTimeoutCounts = new Map();
413
404
  this.deviceProtocolHints = new Map();
405
+ this.sessionProtocols = new Map();
406
+ this.protocolReprobeFailures = new Map();
414
407
  this.protocolV2Assemblers = new Map();
415
408
  this.protocolV2FrameQueues = new Map();
416
409
  this.protocolV2FramePromises = new Map();
@@ -431,11 +424,15 @@ class ReactNativeBleTransport {
431
424
  this.rejectProtocolV2Frames(uuid, new Error(reason));
432
425
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
433
426
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
434
- yield this.release(uuid, true);
427
+ yield this.releaseNative(uuid, true);
435
428
  }
436
429
  }),
437
430
  });
438
431
  this.monitorTokens = new Map();
432
+ this.disconnectEventTokens = new Map();
433
+ this.protocolV2HighVolumeLogSignatures = new Map();
434
+ this.androidHighPriorityDevices = new Set();
435
+ this.androidPriorityResetTimers = new Map();
439
436
  this.nextMonitorToken = 1;
440
437
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
441
438
  }
@@ -449,11 +446,18 @@ class ReactNativeBleTransport {
449
446
  this._messages = messages;
450
447
  }
451
448
  configureProtocolV2(signedData) {
449
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
450
+ if (this.protocolV2SchemaConfiguration === configuration) {
451
+ return;
452
+ }
453
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
452
454
  this._messagesV2 = parseConfigure(signedData);
453
- this.protocolV2Links
454
- .invalidateAllLinks('Protocol V2 schema reconfigured')
455
- .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
456
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
455
+ this.protocolV2SchemaConfiguration = configuration;
456
+ if (isReconfiguration) {
457
+ this.protocolV2Links
458
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
459
+ .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
460
+ }
457
461
  }
458
462
  listen() {
459
463
  }
@@ -464,7 +468,6 @@ class ReactNativeBleTransport {
464
468
  return Promise.resolve(this.blePlxManager);
465
469
  }
466
470
  resolveCharacteristics(device) {
467
- var _a, _b, _c, _d;
468
471
  return __awaiter(this, void 0, void 0, function* () {
469
472
  yield device.discoverAllServicesAndCharacteristics();
470
473
  let infos = tryToGetConfiguration(device);
@@ -481,19 +484,11 @@ class ReactNativeBleTransport {
481
484
  }
482
485
  }
483
486
  }
484
- let fallbackServiceUuid;
485
487
  if (!infos) {
486
488
  const services = yield device.services();
487
489
  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));
488
- const knownService = services.find(service => getInfosForServiceUuid(service.uuid, 'classic'));
489
- const fallbackService = (_a = knownService !== null && knownService !== void 0 ? knownService : services.find(service => !isGenericBleService(service.uuid))) !== null && _a !== void 0 ? _a : services[0];
490
- if (fallbackService) {
491
- fallbackServiceUuid = fallbackService.uuid;
492
- characteristics = yield device.characteristicsForService(fallbackService.uuid);
493
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
494
- }
495
490
  }
496
- if (!infos && !fallbackServiceUuid) {
491
+ if (!infos) {
497
492
  try {
498
493
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
499
494
  yield device.cancelConnection();
@@ -503,9 +498,7 @@ class ReactNativeBleTransport {
503
498
  }
504
499
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
505
500
  }
506
- const serviceUuid = (_b = infos === null || infos === void 0 ? void 0 : infos.serviceUuid) !== null && _b !== void 0 ? _b : fallbackServiceUuid;
507
- const writeUuid = (_c = infos === null || infos === void 0 ? void 0 : infos.writeUuid) !== null && _c !== void 0 ? _c : '00000002-0000-1000-8000-00805f9b34fb';
508
- const notifyUuid = (_d = infos === null || infos === void 0 ? void 0 : infos.notifyUuid) !== null && _d !== void 0 ? _d : '00000003-0000-1000-8000-00805f9b34fb';
501
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
509
502
  if (!serviceUuid) {
510
503
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
511
504
  }
@@ -546,8 +539,8 @@ class ReactNativeBleTransport {
546
539
  attachDisconnectSubscription(transport, device, uuid) {
547
540
  var _a;
548
541
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
542
+ const { monitorToken } = transport;
549
543
  transport.disconnectSubscription = device.onDisconnected(() => {
550
- var _a;
551
544
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
552
545
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
553
546
  return;
@@ -556,17 +549,16 @@ class ReactNativeBleTransport {
556
549
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale transport: ', device === null || device === void 0 ? void 0 : device.id);
557
550
  return;
558
551
  }
552
+ if (this.monitorTokens.get(uuid) !== monitorToken) {
553
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale generation: ', device === null || device === void 0 ? void 0 : device.id);
554
+ return;
555
+ }
559
556
  try {
560
557
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
561
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
562
- name: device === null || device === void 0 ? void 0 : device.name,
563
- id: device === null || device === void 0 ? void 0 : device.id,
564
- connectId: device === null || device === void 0 ? void 0 : device.id,
565
- });
566
- if (this.runPromise) {
558
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
559
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
567
560
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
568
561
  this.runPromise.reject(error);
569
- this.rejectAllProtocolV2Frames(error);
570
562
  }
571
563
  }
572
564
  catch (e) {
@@ -577,6 +569,22 @@ class ReactNativeBleTransport {
577
569
  }
578
570
  });
579
571
  }
572
+ emitDeviceDisconnect(uuid, name, token) {
573
+ var _a;
574
+ if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
575
+ return;
576
+ }
577
+ if (this.monitorTokens.get(uuid) !== token) {
578
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect event ignored for stale generation: ', uuid);
579
+ return;
580
+ }
581
+ this.disconnectEventTokens.set(uuid, token);
582
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
583
+ name,
584
+ id: uuid,
585
+ connectId: uuid,
586
+ });
587
+ }
580
588
  reconnectFirmwareUploadTransport(uuid, transport) {
581
589
  var _a, _b;
582
590
  return __awaiter(this, void 0, void 0, function* () {
@@ -590,19 +598,19 @@ class ReactNativeBleTransport {
590
598
  const isConnected = yield device.isConnected().catch(() => false);
591
599
  if (!isConnected) {
592
600
  try {
593
- device = yield device.connect(connectOptions);
601
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
594
602
  }
595
603
  catch (e) {
596
604
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
597
605
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
598
- device = yield device.connect();
606
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
599
607
  }
600
608
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
601
609
  throw e;
602
610
  }
603
611
  }
604
612
  }
605
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
613
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
606
614
  transport.device = device;
607
615
  transport.writeCharacteristic = writeCharacteristic;
608
616
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -646,13 +654,12 @@ class ReactNativeBleTransport {
646
654
  return;
647
655
  }
648
656
  }
649
- blePlxManager.startDeviceScan(null, {
657
+ blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
650
658
  allowDuplicates: true,
651
659
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
652
660
  }, (error, device) => {
653
- var _a, _b, _c;
661
+ var _a;
654
662
  if (error) {
655
- Log === null || Log === void 0 ? void 0 : Log.debug('ble scan manager: ', blePlxManager);
656
663
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
657
664
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
658
665
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
@@ -672,25 +679,19 @@ class ReactNativeBleTransport {
672
679
  return;
673
680
  }
674
681
  const displayName = getDeviceDisplayName(device);
675
- 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) ||
676
- 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) ||
677
- hasKnownOneKeyService(device);
678
- const shouldTraceCandidate = !!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
679
- if (shouldTraceCandidate) {
680
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] scan candidate', {
682
+ const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
683
+ const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.name) ||
684
+ hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.localName);
685
+ const isOneKey = !isUnnamedIOSPeripheral &&
686
+ !isFindMyPeripheral &&
687
+ hdShared.isOnekeyBluetoothDevice({
688
+ id: device === null || device === void 0 ? void 0 : device.id,
681
689
  name: device === null || device === void 0 ? void 0 : device.name,
682
690
  localName: device === null || device === void 0 ? void 0 : device.localName,
683
- id: device === null || device === void 0 ? void 0 : device.id,
684
- serviceUUIDs: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
685
- accepted: isOneKey,
691
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
686
692
  });
687
- }
688
693
  if (isOneKey) {
689
- Log === null || Log === void 0 ? void 0 : Log.debug('search device start ======================');
690
- const { name, localName, id, serviceUUIDs } = device !== null && device !== void 0 ? device : {};
691
- Log === null || Log === void 0 ? void 0 : Log.debug(`device name: ${name !== null && name !== void 0 ? name : ''}\nlocalName: ${localName !== null && localName !== void 0 ? localName : ''}\nid: ${id !== null && id !== void 0 ? id : ''}\nserviceUUIDs: ${(serviceUUIDs !== null && serviceUUIDs !== void 0 ? serviceUUIDs : []).join(',')}`);
692
694
  addDevice(device);
693
- Log === null || Log === void 0 ? void 0 : Log.debug('search device end ======================\n');
694
695
  }
695
696
  else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
696
697
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
@@ -701,10 +702,23 @@ class ReactNativeBleTransport {
701
702
  });
702
703
  }
703
704
  });
704
- getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
705
+ getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
705
706
  for (const device of devices) {
706
- Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
707
- addDevice(device);
707
+ const localName = 'localName' in device && typeof device.localName === 'string'
708
+ ? device.localName
709
+ : null;
710
+ const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device.name) ||
711
+ hdShared.isPro2FindMyAdvertisementName(localName);
712
+ if (!isFindMyPeripheral &&
713
+ hdShared.isOnekeyBluetoothDevice({
714
+ id: device.id,
715
+ name: device.name,
716
+ localName,
717
+ serviceUuids: device.serviceUUIDs,
718
+ })) {
719
+ Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
720
+ addDevice(device);
721
+ }
708
722
  }
709
723
  });
710
724
  const addDevice = (device) => {
@@ -716,6 +730,12 @@ class ReactNativeBleTransport {
716
730
  this.deviceProtocolHints.set(device.id, protocolHint);
717
731
  }
718
732
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
733
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
734
+ deviceId: device.id,
735
+ name: displayName,
736
+ serviceUUIDs: device.serviceUUIDs,
737
+ protocolHint,
738
+ });
719
739
  }
720
740
  };
721
741
  timer.timeout(() => {
@@ -725,6 +745,57 @@ class ReactNativeBleTransport {
725
745
  }));
726
746
  });
727
747
  }
748
+ installTransportForAcquire(uuid, device, characteristics) {
749
+ return __awaiter(this, void 0, void 0, function* () {
750
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
751
+ const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
752
+ transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
753
+ const monitorToken = this.nextMonitorToken;
754
+ this.nextMonitorToken += 1;
755
+ const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
756
+ transport$1.monitorToken = monitorToken;
757
+ transport$1.notifyTransactionId = notifyTransactionId;
758
+ this.monitorTokens.set(uuid, monitorToken);
759
+ transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
760
+ transportCache[uuid] = transport$1;
761
+ this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
762
+ this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
763
+ if (reactNative.Platform.OS === 'ios') {
764
+ yield new Promise(resolve => {
765
+ setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
766
+ });
767
+ }
768
+ else if (reactNative.Platform.OS === 'android') {
769
+ yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
770
+ }
771
+ const initialMtu = transport$1.mtuSize;
772
+ let refreshAttempts = 0;
773
+ if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
774
+ shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
775
+ refreshAttempts += 1;
776
+ let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
777
+ transport$1.device = refreshedDevice;
778
+ transport$1.mtuSize =
779
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
780
+ if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
781
+ yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
782
+ refreshAttempts += 1;
783
+ refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
784
+ transport$1.device = refreshedDevice;
785
+ transport$1.mtuSize =
786
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
787
+ }
788
+ }
789
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
790
+ platform: reactNative.Platform.OS,
791
+ requested: getRequestedBleMtu(),
792
+ initial: initialMtu,
793
+ actual: transport$1.mtuSize,
794
+ refreshAttempts,
795
+ });
796
+ return transport$1;
797
+ });
798
+ }
728
799
  acquire(input) {
729
800
  var _a, _b;
730
801
  return __awaiter(this, void 0, void 0, function* () {
@@ -749,8 +820,8 @@ class ReactNativeBleTransport {
749
820
  if (forceCleanRunPromise && this.runPromise) {
750
821
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
751
822
  this.runPromise.reject(error);
752
- this.rejectAllProtocolV2Frames(error);
753
823
  this.runPromise = null;
824
+ this.runPromiseDeviceId = null;
754
825
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
755
826
  }
756
827
  const blePlxManager = yield this.getPlxManager();
@@ -783,14 +854,17 @@ class ReactNativeBleTransport {
783
854
  if (!device) {
784
855
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
785
856
  try {
786
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
857
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
787
858
  }
788
859
  catch (e) {
789
860
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
861
+ if (isConnectTimeoutError(e)) {
862
+ throw e;
863
+ }
790
864
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
791
865
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
792
866
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
793
- device = yield blePlxManager.connectToDevice(uuid);
867
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
794
868
  }
795
869
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
796
870
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -806,23 +880,27 @@ class ReactNativeBleTransport {
806
880
  }
807
881
  if (!(yield device.isConnected())) {
808
882
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
883
+ const disconnectedDevice = device;
809
884
  try {
810
- device = yield device.connect(connectOptions);
885
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
811
886
  }
812
887
  catch (e) {
813
888
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
889
+ if (isConnectTimeoutError(e)) {
890
+ throw e;
891
+ }
814
892
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
815
893
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
816
894
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
817
895
  try {
818
- device = yield device.connect();
896
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
819
897
  }
820
898
  catch (e) {
821
899
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
822
900
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
823
901
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
824
- yield device.cancelConnection();
825
- device = yield device.connect();
902
+ yield disconnectedDevice.cancelConnection();
903
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
826
904
  }
827
905
  }
828
906
  }
@@ -831,44 +909,35 @@ class ReactNativeBleTransport {
831
909
  }
832
910
  }
833
911
  }
834
- device = yield requestAndroidMtu(device);
835
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
912
+ device = yield resolveNegotiatedMtu(device);
913
+ const acquiredDevice = device;
914
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
836
915
  const protocolHint = expectedProtocol
837
916
  ? undefined
838
- : (_a = this.deviceProtocolHints.get(uuid)) !== null && _a !== void 0 ? _a : inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
917
+ : (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
839
918
  yield this.release(uuid, true);
840
919
  if (protocolHint) {
841
920
  this.deviceProtocolHints.set(uuid, protocolHint);
842
921
  }
843
- const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
844
- if (reactNative.Platform.OS === 'android') {
845
- transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
846
- }
847
- const monitorToken = this.nextMonitorToken;
848
- this.nextMonitorToken += 1;
849
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
850
- transport$1.monitorToken = monitorToken;
851
- transport$1.notifyTransactionId = notifyTransactionId;
852
- this.monitorTokens.set(uuid, monitorToken);
853
- transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
854
- transportCache[uuid] = transport$1;
855
- this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
856
- if (reactNative.Platform.OS === 'ios') {
857
- yield new Promise(resolve => {
858
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
859
- });
922
+ yield this.installTransportForAcquire(uuid, acquiredDevice, {
923
+ writeCharacteristic,
924
+ notifyCharacteristic,
925
+ });
926
+ try {
927
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
928
+ yield this.installTransportForAcquire(uuid, acquiredDevice);
929
+ }));
930
+ const currentTransport = transportCache[uuid];
931
+ if (!currentTransport) {
932
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
933
+ }
934
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
935
+ return { uuid, protocolType };
860
936
  }
861
- else if (reactNative.Platform.OS === 'android') {
862
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
937
+ catch (error) {
938
+ yield this.release(uuid, true);
939
+ throw error;
863
940
  }
864
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
865
- (_b = this.emitter) === null || _b === void 0 ? void 0 : _b.emit('device-connect', {
866
- name: device.name,
867
- id: device.id,
868
- connectId: device.id,
869
- });
870
- this.attachDisconnectSubscription(transport$1, device, uuid);
871
- return { uuid, protocolType };
872
941
  });
873
942
  }
874
943
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
@@ -887,7 +956,7 @@ class ReactNativeBleTransport {
887
956
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
888
957
  return;
889
958
  }
890
- if (this.deviceProtocol.get(uuid) === 'V2') {
959
+ if (this.getActiveProtocol(uuid) === 'V2') {
891
960
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
892
961
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
893
962
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -905,7 +974,7 @@ class ReactNativeBleTransport {
905
974
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
906
975
  return;
907
976
  }
908
- if (this.runPromise) {
977
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
909
978
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
910
979
  if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
911
980
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -920,13 +989,11 @@ class ReactNativeBleTransport {
920
989
  ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
921
990
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
922
991
  this.runPromise.reject(notifyError);
923
- this.rejectAllProtocolV2Frames(notifyError);
924
992
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
925
993
  return;
926
994
  }
927
995
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
928
996
  this.runPromise.reject(notifyError);
929
- this.rejectAllProtocolV2Frames(notifyError);
930
997
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
931
998
  }
932
999
  return;
@@ -940,7 +1007,7 @@ class ReactNativeBleTransport {
940
1007
  }
941
1008
  try {
942
1009
  const data = buffer.Buffer.from(c.value, 'base64');
943
- const protocol = this.deviceProtocol.get(uuid);
1010
+ const protocol = this.getActiveProtocol(uuid);
944
1011
  if (!protocol) {
945
1012
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
946
1013
  return;
@@ -960,16 +1027,18 @@ class ReactNativeBleTransport {
960
1027
  const value = buffer.Buffer.from(buffer$1);
961
1028
  bufferLength = 0;
962
1029
  buffer$1 = [];
963
- (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1030
+ if (this.runPromiseDeviceId === uuid) {
1031
+ (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1032
+ }
964
1033
  }
965
1034
  }
966
1035
  catch (error) {
967
1036
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
968
1037
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
969
- if (this.deviceProtocol.get(uuid) === 'V2') {
1038
+ if (this.getActiveProtocol(uuid) === 'V2') {
970
1039
  this.rejectProtocolV2Frames(uuid, notifyError);
971
1040
  }
972
- else {
1041
+ else if (this.runPromiseDeviceId === uuid) {
973
1042
  (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
974
1043
  }
975
1044
  }
@@ -977,15 +1046,21 @@ class ReactNativeBleTransport {
977
1046
  return subscription;
978
1047
  }
979
1048
  release(uuid, onclose = false) {
1049
+ return __awaiter(this, void 0, void 0, function* () {
1050
+ yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
1051
+ return this.releaseNative(uuid, onclose);
1052
+ });
1053
+ }
1054
+ releaseNative(uuid, onclose = false) {
980
1055
  var _a, _b, _c, _d, _e, _f, _g;
981
1056
  return __awaiter(this, void 0, void 0, function* () {
982
1057
  const transport = transportCache[uuid];
983
- yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
984
- if (this.runPromise) {
1058
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
985
1059
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
986
1060
  this.runPromise.reject(error);
987
1061
  this.runPromise = null;
988
- this.rejectAllProtocolV2Frames(error);
1062
+ this.runPromiseDeviceId = null;
1063
+ this.rejectProtocolV2Frames(uuid, error);
989
1064
  }
990
1065
  else {
991
1066
  this.resetProtocolV2Frames(uuid);
@@ -995,6 +1070,7 @@ class ReactNativeBleTransport {
995
1070
  this.resetProtocolV2Frames(uuid);
996
1071
  return Promise.resolve(true);
997
1072
  }
1073
+ yield this.restoreAndroidConnectionPriority(uuid, transport);
998
1074
  if (transport) {
999
1075
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
1000
1076
  this.monitorTokens.delete(uuid);
@@ -1015,7 +1091,9 @@ class ReactNativeBleTransport {
1015
1091
  }
1016
1092
  delete transportCache[uuid];
1017
1093
  }
1094
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
1018
1095
  this.deviceProtocol.delete(uuid);
1096
+ this.probingProtocols.delete(uuid);
1019
1097
  (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1020
1098
  this.protocolV2Assemblers.delete(uuid);
1021
1099
  this.resetProtocolV2Frames(uuid);
@@ -1045,23 +1123,11 @@ class ReactNativeBleTransport {
1045
1123
  if (!protocol) {
1046
1124
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1047
1125
  }
1048
- if (name === 'ResourceUpdate' || name === 'ResourceAck') {
1049
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
1050
- file_name: data === null || data === void 0 ? void 0 : data.file_name,
1051
- hash: data === null || data === void 0 ? void 0 : data.hash,
1052
- });
1053
- }
1054
- else if (transport.LogBlockCommand.has(name)) {
1055
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
1056
- }
1057
- else {
1058
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
1059
- }
1126
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
1060
1127
  if (protocol === 'V2') {
1061
1128
  return this.callProtocolV2(uuid, name, data, options);
1062
1129
  }
1063
1130
  const forceRun = name === 'Initialize' || name === 'Cancel';
1064
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native call this.runPromise', this.runPromise);
1065
1131
  if (this.runPromise && !forceRun) {
1066
1132
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1067
1133
  }
@@ -1076,7 +1142,19 @@ class ReactNativeBleTransport {
1076
1142
  const transport = this.getCachedTransport(uuid);
1077
1143
  const runPromise = hdShared.createDeferred();
1078
1144
  runPromise.promise.catch(() => undefined);
1145
+ const supersededRunPromise = this.runPromise;
1146
+ if (supersededRunPromise) {
1147
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1148
+ }
1079
1149
  this.runPromise = runPromise;
1150
+ this.runPromiseDeviceId = uuid;
1151
+ const releaseOwnershipIfCurrent = () => {
1152
+ if (this.runPromise === runPromise) {
1153
+ this.runPromise = null;
1154
+ this.runPromiseDeviceId = null;
1155
+ }
1156
+ };
1157
+ const isCurrentOwner = () => this.runPromise === runPromise;
1080
1158
  const messages = this._messages;
1081
1159
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1082
1160
  let timeout;
@@ -1097,6 +1175,9 @@ class ReactNativeBleTransport {
1097
1175
  }
1098
1176
  catch (e) {
1099
1177
  onError(e);
1178
+ if (isWedgedWriteError(e)) {
1179
+ throw e;
1180
+ }
1100
1181
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1101
1182
  }
1102
1183
  }
@@ -1124,6 +1205,9 @@ class ReactNativeBleTransport {
1124
1205
  }
1125
1206
  catch (e) {
1126
1207
  onError(e);
1208
+ if (isWedgedWriteError(e)) {
1209
+ throw e;
1210
+ }
1127
1211
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1128
1212
  }
1129
1213
  }
@@ -1134,13 +1218,13 @@ class ReactNativeBleTransport {
1134
1218
  });
1135
1219
  }
1136
1220
  if (name === 'EmmcFileWrite') {
1137
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1138
- this.runPromise = null;
1221
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1222
+ releaseOwnershipIfCurrent();
1139
1223
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1140
1224
  });
1141
1225
  }
1142
1226
  else if (name === 'FirmwareUpload') {
1143
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
1227
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
1144
1228
  packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
1145
1229
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1146
1230
  pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
@@ -1151,7 +1235,7 @@ class ReactNativeBleTransport {
1151
1235
  let attempt = 0;
1152
1236
  while (true) {
1153
1237
  try {
1154
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1238
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1155
1239
  return;
1156
1240
  }
1157
1241
  catch (error) {
@@ -1159,36 +1243,18 @@ class ReactNativeBleTransport {
1159
1243
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1160
1244
  throw error;
1161
1245
  }
1162
- const shouldReconnect = retryType === 'reconnectable';
1163
- const delayMs = shouldReconnect
1164
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1165
- : resolveFirmwareUploadRetryDelay(attempt);
1246
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1166
1247
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1167
1248
  attempt: attempt + 1,
1168
1249
  delayMs,
1169
- reconnect: shouldReconnect,
1170
1250
  error,
1171
1251
  });
1172
- if (shouldReconnect) {
1173
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1174
- }
1175
1252
  yield delay(delayMs);
1176
1253
  attempt += 1;
1177
- if (shouldReconnect) {
1178
- try {
1179
- yield this.reconnectFirmwareUploadTransport(uuid, transport);
1180
- }
1181
- catch (e) {
1182
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
1183
- if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1184
- throw e;
1185
- }
1186
- }
1187
- }
1188
1254
  }
1189
1255
  }
1190
1256
  }), e => {
1191
- this.runPromise = null;
1257
+ releaseOwnershipIfCurrent();
1192
1258
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1193
1259
  });
1194
1260
  }
@@ -1196,11 +1262,17 @@ class ReactNativeBleTransport {
1196
1262
  for (const o of buffers) {
1197
1263
  const outData = o.toString('base64');
1198
1264
  try {
1199
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1265
+ const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1266
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1267
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1268
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1200
1269
  }
1201
1270
  catch (e) {
1202
1271
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1203
- this.runPromise = null;
1272
+ releaseOwnershipIfCurrent();
1273
+ if (isWedgedWriteError(e)) {
1274
+ throw e;
1275
+ }
1204
1276
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1205
1277
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1206
1278
  }
@@ -1229,17 +1301,23 @@ class ReactNativeBleTransport {
1229
1301
  if (typeof response !== 'string') {
1230
1302
  throw new Error('Returning data is not string.');
1231
1303
  }
1232
- Log === null || Log === void 0 ? void 0 : Log.debug('receive data: ', response);
1233
1304
  const jsonData = ProtocolV1.decodeMessage(messages, response);
1234
1305
  return check.call(jsonData);
1235
1306
  }
1236
1307
  catch (e) {
1237
- if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1238
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
1308
+ if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1309
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1239
1310
  }
1240
1311
  else {
1241
1312
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1242
1313
  }
1314
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1315
+ const isStaleCall = this.runPromise !== runPromise;
1316
+ if (!isProbeTimeout &&
1317
+ !isStaleCall &&
1318
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1319
+ yield this.disconnect(uuid);
1320
+ }
1243
1321
  throw e;
1244
1322
  }
1245
1323
  finally {
@@ -1247,6 +1325,7 @@ class ReactNativeBleTransport {
1247
1325
  clearTimeout(timeout);
1248
1326
  if (this.runPromise === runPromise) {
1249
1327
  this.runPromise = null;
1328
+ this.runPromiseDeviceId = null;
1250
1329
  }
1251
1330
  }
1252
1331
  });
@@ -1257,9 +1336,9 @@ class ReactNativeBleTransport {
1257
1336
  disconnect(session) {
1258
1337
  var _a, _b, _c, _d, _e;
1259
1338
  return __awaiter(this, void 0, void 0, function* () {
1260
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport resetSession: ', session);
1261
1339
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1262
1340
  const transport = transportCache[session];
1341
+ const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
1263
1342
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1264
1343
  try {
1265
1344
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1272,7 +1351,7 @@ class ReactNativeBleTransport {
1272
1351
  }
1273
1352
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1274
1353
  try {
1275
- 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);
1354
+ 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);
1276
1355
  transport.notifySubscription.remove();
1277
1356
  transport.notifySubscription = undefined;
1278
1357
  }
@@ -1282,7 +1361,7 @@ class ReactNativeBleTransport {
1282
1361
  }
1283
1362
  if (session) {
1284
1363
  try {
1285
- yield ((_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelTransaction(session));
1364
+ yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(session));
1286
1365
  }
1287
1366
  catch (e) {
1288
1367
  Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -1297,7 +1376,7 @@ class ReactNativeBleTransport {
1297
1376
  }
1298
1377
  }
1299
1378
  try {
1300
- yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelDeviceConnection(session));
1379
+ yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelDeviceConnection(session));
1301
1380
  }
1302
1381
  catch (e) {
1303
1382
  Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: manager.cancelDeviceConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -1306,19 +1385,21 @@ class ReactNativeBleTransport {
1306
1385
  delete transportCache[session];
1307
1386
  }
1308
1387
  this.deviceProtocol.delete(session);
1388
+ this.probingProtocols.delete(session);
1309
1389
  this.deviceProtocolHints.delete(session);
1390
+ this.sessionProtocols.delete(session);
1391
+ this.protocolReprobeFailures.delete(session);
1310
1392
  this.protocolV2Assemblers.delete(session);
1311
1393
  this.resetProtocolV2Frames(session);
1312
1394
  try {
1313
- (_d = this.emitter) === null || _d === void 0 ? void 0 : _d.emit('device-disconnect', {
1314
- name: (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name,
1315
- id: session,
1316
- connectId: session,
1317
- });
1395
+ this.emitDeviceDisconnect(session, (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name, monitorToken);
1318
1396
  }
1319
1397
  catch (e) {
1320
1398
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1321
1399
  }
1400
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1401
+ this.monitorTokens.delete(session);
1402
+ }
1322
1403
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1323
1404
  });
1324
1405
  }
@@ -1326,6 +1407,92 @@ class ReactNativeBleTransport {
1326
1407
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1327
1408
  if (this.runPromise) ;
1328
1409
  this.runPromise = null;
1410
+ this.runPromiseDeviceId = null;
1411
+ }
1412
+ connectWithTimeout(uuid, connect) {
1413
+ return __awaiter(this, void 0, void 0, function* () {
1414
+ let timer;
1415
+ let timedOut = false;
1416
+ const pending = connect();
1417
+ pending.catch(() => undefined);
1418
+ try {
1419
+ const result = yield Promise.race([
1420
+ pending,
1421
+ new Promise((_, reject) => {
1422
+ timer = setTimeout(() => {
1423
+ timedOut = true;
1424
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1425
+ }, BLE_CONNECT_TIMEOUT_MS);
1426
+ }),
1427
+ ]);
1428
+ return result;
1429
+ }
1430
+ catch (error) {
1431
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1432
+ this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1433
+ }
1434
+ throw error;
1435
+ }
1436
+ finally {
1437
+ if (timer)
1438
+ clearTimeout(timer);
1439
+ }
1440
+ });
1441
+ }
1442
+ resolveCharacteristicsWithTimeout(uuid, device) {
1443
+ return __awaiter(this, void 0, void 0, function* () {
1444
+ let timer;
1445
+ let timedOut = false;
1446
+ const pending = this.resolveCharacteristics(device);
1447
+ pending.catch(() => undefined);
1448
+ try {
1449
+ const result = yield Promise.race([
1450
+ pending,
1451
+ new Promise((_, reject) => {
1452
+ timer = setTimeout(() => {
1453
+ timedOut = true;
1454
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1455
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1456
+ }),
1457
+ ]);
1458
+ this.connectionSetupTimeoutCounts.delete(uuid);
1459
+ return result;
1460
+ }
1461
+ catch (error) {
1462
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1463
+ this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1464
+ }
1465
+ throw error;
1466
+ }
1467
+ finally {
1468
+ if (timer)
1469
+ clearTimeout(timer);
1470
+ }
1471
+ });
1472
+ }
1473
+ abandonStalledConnection(uuid, stage) {
1474
+ var _a, _b;
1475
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1476
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1477
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1478
+ stage,
1479
+ setupTimeoutsSinceSuccess: timeouts,
1480
+ });
1481
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1482
+ });
1483
+ const stalled = transportCache[uuid];
1484
+ if (stalled) {
1485
+ delete transportCache[uuid];
1486
+ }
1487
+ this.deviceProtocol.delete(uuid);
1488
+ this.probingProtocols.delete(uuid);
1489
+ this.protocolV2Assemblers.delete(uuid);
1490
+ this.resetProtocolV2Frames(uuid);
1491
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1492
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1493
+ this.resetPlxManager();
1494
+ this.connectionSetupTimeoutCounts.delete(uuid);
1495
+ }
1329
1496
  }
1330
1497
  getCachedTransport(uuid) {
1331
1498
  const transport = transportCache[uuid];
@@ -1334,46 +1501,179 @@ class ReactNativeBleTransport {
1334
1501
  }
1335
1502
  return transport;
1336
1503
  }
1504
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1505
+ return __awaiter(this, void 0, void 0, function* () {
1506
+ let timer;
1507
+ let timedOut = false;
1508
+ try {
1509
+ yield Promise.race([
1510
+ write(data),
1511
+ new Promise((_, reject) => {
1512
+ timer = setTimeout(() => {
1513
+ timedOut = true;
1514
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1515
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1516
+ }),
1517
+ ]);
1518
+ this.writeTimeoutCounts.delete(uuid);
1519
+ }
1520
+ catch (error) {
1521
+ if (timedOut) {
1522
+ if (isCurrentOwner && !isCurrentOwner()) {
1523
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1524
+ }
1525
+ else {
1526
+ this.tearDownWedgedLink(uuid);
1527
+ }
1528
+ }
1529
+ throw error;
1530
+ }
1531
+ finally {
1532
+ if (timer)
1533
+ clearTimeout(timer);
1534
+ }
1535
+ });
1536
+ }
1537
+ tearDownWedgedLink(uuid) {
1538
+ var _a;
1539
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1540
+ this.writeTimeoutCounts.set(uuid, timeouts);
1541
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1542
+ consecutiveWriteTimeouts: timeouts,
1543
+ });
1544
+ const wedged = transportCache[uuid];
1545
+ this.disconnect(uuid).catch(error => {
1546
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1547
+ });
1548
+ if (wedged && transportCache[uuid] === wedged) {
1549
+ delete transportCache[uuid];
1550
+ }
1551
+ this.deviceProtocol.delete(uuid);
1552
+ this.probingProtocols.delete(uuid);
1553
+ this.protocolV2Assemblers.delete(uuid);
1554
+ this.resetProtocolV2Frames(uuid);
1555
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1556
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1557
+ this.resetPlxManager();
1558
+ this.writeTimeoutCounts.delete(uuid);
1559
+ }
1560
+ }
1561
+ resetPlxManager() {
1562
+ const manager = this.blePlxManager;
1563
+ this.blePlxManager = undefined;
1564
+ Object.keys(transportCache).forEach(key => {
1565
+ delete transportCache[key];
1566
+ });
1567
+ this.deviceProtocol.clear();
1568
+ this.probingProtocols.clear();
1569
+ this.sessionProtocols.clear();
1570
+ this.protocolReprobeFailures.clear();
1571
+ this.writeTimeoutCounts.clear();
1572
+ this.connectionSetupTimeoutCounts.clear();
1573
+ this.monitorTokens.clear();
1574
+ this.protocolV2Assemblers.clear();
1575
+ try {
1576
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1577
+ }
1578
+ catch (error) {
1579
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1580
+ }
1581
+ }
1337
1582
  createProtocolMismatchError(expected) {
1338
1583
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1339
1584
  }
1340
1585
  createProtocolDetectionError() {
1341
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1586
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1342
1587
  }
1343
1588
  clearProbeProtocol(uuid, protocol) {
1589
+ if (this.probingProtocols.get(uuid) === protocol) {
1590
+ this.probingProtocols.delete(uuid);
1591
+ }
1344
1592
  if (this.deviceProtocol.get(uuid) === protocol) {
1345
1593
  this.deviceProtocol.delete(uuid);
1346
1594
  }
1347
1595
  }
1348
- detectProtocol(uuid, expectedProtocol, protocolHint) {
1596
+ getActiveProtocol(uuid) {
1597
+ var _a;
1598
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1599
+ }
1600
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1601
+ var _a;
1349
1602
  return __awaiter(this, void 0, void 0, function* () {
1603
+ if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
1604
+ this.deviceProtocol.set(uuid, expectedProtocol);
1605
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1606
+ deviceId: uuid,
1607
+ protocol: expectedProtocol,
1608
+ source: 'expected',
1609
+ });
1610
+ return expectedProtocol;
1611
+ }
1350
1612
  if (expectedProtocol === 'V1') {
1351
1613
  if (yield this.probeProtocolV1(uuid)) {
1352
1614
  this.deviceProtocol.set(uuid, 'V1');
1353
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
1615
+ this.sessionProtocols.set(uuid, 'V1');
1616
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1617
+ deviceId: uuid,
1618
+ protocol: 'V1',
1619
+ source: 'expected',
1620
+ });
1354
1621
  return 'V1';
1355
1622
  }
1356
1623
  throw this.createProtocolMismatchError(expectedProtocol);
1357
1624
  }
1358
1625
  if (expectedProtocol === 'V2') {
1359
- this.deviceProtocol.set(uuid, 'V2');
1360
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1361
- return 'V2';
1626
+ if (yield this.probeProtocolV2(uuid)) {
1627
+ this.deviceProtocol.set(uuid, 'V2');
1628
+ this.sessionProtocols.set(uuid, 'V2');
1629
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1630
+ deviceId: uuid,
1631
+ protocol: 'V2',
1632
+ source: 'expected',
1633
+ });
1634
+ return 'V2';
1635
+ }
1636
+ throw this.createProtocolMismatchError(expectedProtocol);
1362
1637
  }
1363
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1638
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1639
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1640
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1641
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1642
+ !protocolHint &&
1643
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1644
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1364
1645
  for (let i = 0; i < probeOrder.length; i += 1) {
1365
1646
  const protocol = probeOrder[i];
1366
1647
  if (i > 0) {
1367
1648
  yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1649
+ if (!transportCache[uuid]) {
1650
+ if (!rebuildTransport) {
1651
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1652
+ }
1653
+ yield rebuildTransport();
1654
+ }
1368
1655
  }
1369
1656
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1370
1657
  if (detected) {
1371
1658
  this.deviceProtocol.set(uuid, protocol);
1372
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
1659
+ this.sessionProtocols.set(uuid, protocol);
1660
+ this.protocolReprobeFailures.delete(uuid);
1661
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1662
+ deviceId: uuid,
1663
+ protocol,
1664
+ source: 'probe',
1665
+ });
1373
1666
  return protocol;
1374
1667
  }
1375
1668
  }
1669
+ if (trustSessionProtocol) {
1670
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1671
+ }
1672
+ else {
1673
+ this.protocolReprobeFailures.delete(uuid);
1674
+ }
1376
1675
  this.deviceProtocol.delete(uuid);
1676
+ this.probingProtocols.delete(uuid);
1377
1677
  throw this.createProtocolDetectionError();
1378
1678
  });
1379
1679
  }
@@ -1425,13 +1725,17 @@ class ReactNativeBleTransport {
1425
1725
  return false;
1426
1726
  }
1427
1727
  try {
1428
- this.deviceProtocol.set(uuid, 'V1');
1429
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1728
+ this.probingProtocols.set(uuid, 'V1');
1729
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1730
+ this.probingProtocols.delete(uuid);
1430
1731
  return true;
1431
1732
  }
1432
1733
  catch (error) {
1433
1734
  this.clearProbeProtocol(uuid, 'V1');
1434
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1735
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1736
+ if (isWedgedWriteError(error)) {
1737
+ throw error;
1738
+ }
1435
1739
  return false;
1436
1740
  }
1437
1741
  });
@@ -1442,7 +1746,7 @@ class ReactNativeBleTransport {
1442
1746
  if (!this._messages || !this._messagesV2) {
1443
1747
  return false;
1444
1748
  }
1445
- this.deviceProtocol.set(uuid, 'V2');
1749
+ this.probingProtocols.set(uuid, 'V2');
1446
1750
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1447
1751
  const detected = yield transport.probeProtocolV2({
1448
1752
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1458,6 +1762,9 @@ class ReactNativeBleTransport {
1458
1762
  if (!detected) {
1459
1763
  this.clearProbeProtocol(uuid, 'V2');
1460
1764
  }
1765
+ else {
1766
+ this.probingProtocols.delete(uuid);
1767
+ }
1461
1768
  return detected;
1462
1769
  });
1463
1770
  }
@@ -1500,16 +1807,8 @@ class ReactNativeBleTransport {
1500
1807
  }
1501
1808
  this.getProtocolV2FrameQueue(uuid).push(frame);
1502
1809
  }
1503
- rejectAllProtocolV2Frames(error) {
1504
- this.protocolV2FrameQueues.clear();
1505
- for (const framePromise of this.protocolV2FramePromises.values()) {
1506
- framePromise.reject(error);
1507
- }
1508
- this.protocolV2FramePromises.clear();
1509
- }
1510
1810
  resetProtocolV2Frames(uuid) {
1511
- this.protocolV2FrameQueues.delete(uuid);
1512
- this.protocolV2FramePromises.delete(uuid);
1811
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1513
1812
  }
1514
1813
  rejectProtocolV2Frames(uuid, error) {
1515
1814
  this.protocolV2FrameQueues.delete(uuid);
@@ -1537,38 +1836,70 @@ class ReactNativeBleTransport {
1537
1836
  }
1538
1837
  });
1539
1838
  }
1540
- writeProtocolV2Frame(transport, frame, options) {
1839
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1541
1840
  return __awaiter(this, void 0, void 0, function* () {
1542
- const tuning = getProtocolV2BleTuning();
1543
- const packetCapacity = resolveProtocolV2PacketCapacity({
1841
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
1544
1842
  platform: reactNative.Platform.OS,
1545
- iosPacketLength: tuning.iosPacketLength,
1546
- androidPacketLength: tuning.androidPacketLength,
1547
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
1843
+ highThroughput: context.highThroughput,
1844
+ requestedWithResponse: context.writeWithResponse,
1845
+ characteristic: transport.writeCharacteristic,
1548
1846
  });
1549
- const writeWithResponse = !!(options === null || options === void 0 ? void 0 : options.writeWithResponse) || (!!(options === null || options === void 0 ? void 0 : options.highVolume) && tuning.highVolumeWriteWithResponse);
1550
- const writeMode = resolveBleWriteMode(transport.writeCharacteristic, writeWithResponse ? 'withResponse' : 'withoutResponse');
1551
- const shouldThrottle = !!(options === null || options === void 0 ? void 0 : options.highVolume) && writeMode === 'withoutResponse';
1552
- let packetsWritten = 0;
1553
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1554
- const chunk = frame.slice(offset, offset + packetCapacity);
1555
- const base64 = buffer.Buffer.from(chunk).toString('base64');
1556
- if (writeMode === 'withResponse') {
1557
- yield transport.writeCharacteristic.writeWithResponse(base64);
1847
+ let attempt = 0;
1848
+ for (;;) {
1849
+ assertCurrentGeneration();
1850
+ if (context.signal.aborted) {
1851
+ throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1558
1852
  }
1559
- else {
1560
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1853
+ try {
1854
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
1855
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1856
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1857
+ try {
1858
+ assertCurrentGeneration();
1859
+ return !context.signal.aborted;
1860
+ }
1861
+ catch (_a) {
1862
+ return false;
1863
+ }
1864
+ });
1865
+ assertCurrentGeneration();
1866
+ return;
1561
1867
  }
1562
- packetsWritten += 1;
1563
- if (shouldThrottle &&
1564
- packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
1565
- offset + packetCapacity < frame.length) {
1566
- yield delay(tuning.highVolumeWritePauseMs);
1868
+ catch (error) {
1869
+ if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
1870
+ attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1871
+ throw error;
1872
+ }
1873
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1874
+ attempt += 1;
1875
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
1876
+ name: context.messageName,
1877
+ attempt,
1878
+ delayMs,
1879
+ });
1880
+ yield delay(delayMs);
1567
1881
  }
1568
1882
  }
1569
- if (shouldThrottle) {
1570
- yield delay(tuning.highVolumeWriteFlushDelayMs);
1571
- }
1883
+ });
1884
+ }
1885
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1886
+ return __awaiter(this, void 0, void 0, function* () {
1887
+ const tuning = getProtocolV2BleTuning();
1888
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1889
+ platform: reactNative.Platform.OS,
1890
+ iosPacketLength: tuning.iosPacketLength,
1891
+ androidPacketLength: tuning.androidPacketLength,
1892
+ mtu: transport$1.mtuSize,
1893
+ });
1894
+ yield transport.writeProtocolV2BleFrame({
1895
+ frame,
1896
+ packetCapacity,
1897
+ assertActive: assertCurrentGeneration,
1898
+ signal: context.signal,
1899
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1900
+ wait: delay,
1901
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1902
+ });
1572
1903
  });
1573
1904
  }
1574
1905
  callProtocolV2(uuid, name, data, options) {
@@ -1577,17 +1908,40 @@ class ReactNativeBleTransport {
1577
1908
  if (!this._messages || !this._messagesV2) {
1578
1909
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1579
1910
  }
1580
- 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 });
1581
- const highVolumeWrite = transport.LogBlockCommand.has(name);
1582
- if (highVolumeWrite) {
1911
+ const callOptions = options;
1912
+ const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
1913
+ if (highThroughputWrite) {
1914
+ yield this.ensureProtocolV2HighThroughputMtu(uuid);
1583
1915
  const tuning = getProtocolV2BleTuning();
1584
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:', name, {
1585
- packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1586
- burstSize: tuning.highVolumeWriteBurstSize,
1587
- pauseMs: tuning.highVolumeWritePauseMs,
1588
- flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
1589
- writeWithResponse: tuning.highVolumeWriteWithResponse,
1916
+ const currentTransport = this.getCachedTransport(uuid);
1917
+ const writeWithResponse = shouldWriteProtocolV2WithResponse({
1918
+ platform: reactNative.Platform.OS,
1919
+ highThroughput: true,
1920
+ requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
1921
+ characteristic: currentTransport.writeCharacteristic,
1590
1922
  });
1923
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1924
+ platform: reactNative.Platform.OS,
1925
+ iosPacketLength: tuning.iosPacketLength,
1926
+ androidPacketLength: tuning.androidPacketLength,
1927
+ mtu: currentTransport.mtuSize,
1928
+ });
1929
+ const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
1930
+ const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
1931
+ const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
1932
+ if (!loggedSignatures.has(logSignature)) {
1933
+ loggedSignatures.add(logSignature);
1934
+ this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
1935
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1936
+ name,
1937
+ writeMode,
1938
+ reportedMtu: currentTransport.mtuSize,
1939
+ packetCapacity,
1940
+ });
1941
+ }
1942
+ }
1943
+ if (highThroughputWrite) {
1944
+ yield this.enableAndroidHighConnectionPriority(uuid);
1591
1945
  }
1592
1946
  try {
1593
1947
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
@@ -1596,6 +1950,85 @@ class ReactNativeBleTransport {
1596
1950
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1597
1951
  throw e;
1598
1952
  }
1953
+ finally {
1954
+ if (highThroughputWrite) {
1955
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
1956
+ }
1957
+ }
1958
+ });
1959
+ }
1960
+ ensureProtocolV2HighThroughputMtu(uuid) {
1961
+ return __awaiter(this, void 0, void 0, function* () {
1962
+ const transport = this.getCachedTransport(uuid);
1963
+ if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
1964
+ return;
1965
+ const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
1966
+ transport.device = refreshedDevice;
1967
+ transport.mtuSize =
1968
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
1969
+ if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
1970
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`);
1971
+ }
1972
+ });
1973
+ }
1974
+ clearAndroidPriorityResetTimer(uuid) {
1975
+ const timerId = this.androidPriorityResetTimers.get(uuid);
1976
+ if (timerId !== undefined) {
1977
+ clearTimeout(timerId);
1978
+ this.androidPriorityResetTimers.delete(uuid);
1979
+ }
1980
+ }
1981
+ enableAndroidHighConnectionPriority(uuid) {
1982
+ return __awaiter(this, void 0, void 0, function* () {
1983
+ if (reactNative.Platform.OS !== 'android')
1984
+ return;
1985
+ this.clearAndroidPriorityResetTimer(uuid);
1986
+ if (this.androidHighPriorityDevices.has(uuid))
1987
+ return;
1988
+ const transport = transportCache[uuid];
1989
+ if (!transport)
1990
+ return;
1991
+ try {
1992
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
1993
+ this.androidHighPriorityDevices.add(uuid);
1994
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
1995
+ priority: 'high',
1996
+ });
1997
+ }
1998
+ catch (error) {
1999
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
2000
+ error: error instanceof Error ? error.message : String(error),
2001
+ });
2002
+ }
2003
+ });
2004
+ }
2005
+ scheduleAndroidBalancedConnectionPriority(uuid) {
2006
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
2007
+ return;
2008
+ this.clearAndroidPriorityResetTimer(uuid);
2009
+ const timerId = setTimeout(() => {
2010
+ this.androidPriorityResetTimers.delete(uuid);
2011
+ this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
2012
+ }, ANDROID_HIGH_PRIORITY_IDLE_MS);
2013
+ this.androidPriorityResetTimers.set(uuid, timerId);
2014
+ }
2015
+ restoreAndroidConnectionPriority(uuid, transport) {
2016
+ return __awaiter(this, void 0, void 0, function* () {
2017
+ this.clearAndroidPriorityResetTimer(uuid);
2018
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
2019
+ return;
2020
+ }
2021
+ try {
2022
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
2023
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2024
+ priority: 'balanced',
2025
+ });
2026
+ }
2027
+ catch (error) {
2028
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
2029
+ error: error instanceof Error ? error.message : String(error),
2030
+ });
2031
+ }
1599
2032
  });
1600
2033
  }
1601
2034
  createProtocolV2Adapter(uuid) {
@@ -1619,9 +2052,7 @@ class ReactNativeBleTransport {
1619
2052
  writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1620
2053
  assertCurrentGeneration();
1621
2054
  const currentTransport = this.getCachedTransport(uuid);
1622
- yield this.writeProtocolV2Frame(currentTransport, frame, {
1623
- highVolume: context.highVolume,
1624
- });
2055
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1625
2056
  }),
1626
2057
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1627
2058
  assertCurrentGeneration();
@@ -1642,11 +2073,18 @@ class ReactNativeBleTransport {
1642
2073
  };
1643
2074
  }
1644
2075
  getProtocolType(path) {
1645
- return this.deviceProtocol.get(path);
2076
+ return this.getActiveProtocol(path);
1646
2077
  }
1647
2078
  }
1648
2079
 
2080
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2081
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2082
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2083
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2084
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2085
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1649
2086
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1650
2087
  exports["default"] = ReactNativeBleTransport;
2088
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1651
2089
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1652
2090
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;