@onekeyfe/hd-transport-react-native 1.2.0-alpha.12 → 1.2.0-alpha.121

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,23 @@ 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 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;
296
286
  const IOS_NOTIFY_READY_DELAY_MS = 150;
297
287
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
298
288
  const DEFAULT_PROTOCOL_V2_BLE_TUNING = {
299
- iosPacketLength: IOS_PACKET_LENGTH,
300
- androidPacketLength: ANDROID_PACKET_LENGTH,
289
+ iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
290
+ androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
301
291
  };
302
292
  let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
303
293
  const normalizePositiveInteger = (value, fallback) => {
@@ -326,19 +316,29 @@ function inferProtocolHintFromDeviceName(name) {
326
316
  function getDeviceDisplayName(device) {
327
317
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
328
318
  }
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;
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;
337
325
  const connectOptions = {
338
- requestMTU: ANDROID_REQUEST_MTU,
339
- timeout: 3000,
326
+ requestMTU: getRequestedBleMtu(),
327
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
340
328
  refreshGatt: 'OnConnected',
341
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;
342
342
  const tryToGetConfiguration = (device) => {
343
343
  if (!device || !device.serviceUUIDs)
344
344
  return null;
@@ -350,23 +350,25 @@ const tryToGetConfiguration = (device) => {
350
350
  return null;
351
351
  return infos;
352
352
  };
353
- const requestAndroidMtu = (device) => __awaiter(void 0, void 0, void 0, function* () {
354
- 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')
355
355
  return device;
356
356
  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
- });
357
+ const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
363
358
  return mtuDevice;
364
359
  }
365
360
  catch (error) {
366
- 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
+ });
367
368
  return device;
368
369
  }
369
370
  });
371
+ const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
370
372
  function remapError(error) {
371
373
  var _a;
372
374
  if (error instanceof reactNativeBlePlx.BleError) {
@@ -393,9 +395,15 @@ class ReactNativeBleTransport {
393
395
  this.stopped = false;
394
396
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
395
397
  this.runPromise = null;
398
+ this.runPromiseDeviceId = null;
396
399
  this.firmwareUploadWriteRecoveryIds = new Set();
397
400
  this.deviceProtocol = new Map();
401
+ this.probingProtocols = new Map();
402
+ this.writeTimeoutCounts = new Map();
403
+ this.connectionSetupTimeoutCounts = new Map();
398
404
  this.deviceProtocolHints = new Map();
405
+ this.sessionProtocols = new Map();
406
+ this.protocolReprobeFailures = new Map();
399
407
  this.protocolV2Assemblers = new Map();
400
408
  this.protocolV2FrameQueues = new Map();
401
409
  this.protocolV2FramePromises = new Map();
@@ -416,11 +424,15 @@ class ReactNativeBleTransport {
416
424
  this.rejectProtocolV2Frames(uuid, new Error(reason));
417
425
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
418
426
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
419
- yield this.release(uuid, true);
427
+ yield this.releaseNative(uuid, true);
420
428
  }
421
429
  }),
422
430
  });
423
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();
424
436
  this.nextMonitorToken = 1;
425
437
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
426
438
  }
@@ -434,10 +446,18 @@ class ReactNativeBleTransport {
434
446
  this._messages = messages;
435
447
  }
436
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;
437
454
  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));
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
+ }
441
461
  }
442
462
  listen() {
443
463
  }
@@ -448,7 +468,6 @@ class ReactNativeBleTransport {
448
468
  return Promise.resolve(this.blePlxManager);
449
469
  }
450
470
  resolveCharacteristics(device) {
451
- var _a, _b, _c, _d;
452
471
  return __awaiter(this, void 0, void 0, function* () {
453
472
  yield device.discoverAllServicesAndCharacteristics();
454
473
  let infos = tryToGetConfiguration(device);
@@ -465,19 +484,11 @@ class ReactNativeBleTransport {
465
484
  }
466
485
  }
467
486
  }
468
- let fallbackServiceUuid;
469
487
  if (!infos) {
470
488
  const services = yield device.services();
471
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));
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
490
  }
480
- if (!infos && !fallbackServiceUuid) {
491
+ if (!infos) {
481
492
  try {
482
493
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
483
494
  yield device.cancelConnection();
@@ -487,9 +498,7 @@ class ReactNativeBleTransport {
487
498
  }
488
499
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
489
500
  }
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';
501
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
493
502
  if (!serviceUuid) {
494
503
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
495
504
  }
@@ -530,8 +539,8 @@ class ReactNativeBleTransport {
530
539
  attachDisconnectSubscription(transport, device, uuid) {
531
540
  var _a;
532
541
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
542
+ const { monitorToken } = transport;
533
543
  transport.disconnectSubscription = device.onDisconnected(() => {
534
- var _a;
535
544
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
536
545
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
537
546
  return;
@@ -540,17 +549,16 @@ class ReactNativeBleTransport {
540
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);
541
550
  return;
542
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
+ }
543
556
  try {
544
557
  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) {
558
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
559
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
551
560
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
552
561
  this.runPromise.reject(error);
553
- this.rejectAllProtocolV2Frames(error);
554
562
  }
555
563
  }
556
564
  catch (e) {
@@ -561,6 +569,22 @@ class ReactNativeBleTransport {
561
569
  }
562
570
  });
563
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
+ }
564
588
  reconnectFirmwareUploadTransport(uuid, transport) {
565
589
  var _a, _b;
566
590
  return __awaiter(this, void 0, void 0, function* () {
@@ -574,19 +598,19 @@ class ReactNativeBleTransport {
574
598
  const isConnected = yield device.isConnected().catch(() => false);
575
599
  if (!isConnected) {
576
600
  try {
577
- device = yield device.connect(connectOptions);
601
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
578
602
  }
579
603
  catch (e) {
580
604
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
581
605
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
582
- device = yield device.connect();
606
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
583
607
  }
584
608
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
585
609
  throw e;
586
610
  }
587
611
  }
588
612
  }
589
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
613
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
590
614
  transport.device = device;
591
615
  transport.writeCharacteristic = writeCharacteristic;
592
616
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -630,11 +654,11 @@ class ReactNativeBleTransport {
630
654
  return;
631
655
  }
632
656
  }
633
- blePlxManager.startDeviceScan(null, {
657
+ blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
634
658
  allowDuplicates: true,
635
659
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
636
660
  }, (error, device) => {
637
- var _a, _b, _c;
661
+ var _a;
638
662
  if (error) {
639
663
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
640
664
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
@@ -655,9 +679,17 @@ class ReactNativeBleTransport {
655
679
  return;
656
680
  }
657
681
  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);
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,
689
+ name: device === null || device === void 0 ? void 0 : device.name,
690
+ localName: device === null || device === void 0 ? void 0 : device.localName,
691
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
692
+ });
661
693
  if (isOneKey) {
662
694
  addDevice(device);
663
695
  }
@@ -670,9 +702,23 @@ class ReactNativeBleTransport {
670
702
  });
671
703
  }
672
704
  });
673
- getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
705
+ getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
674
706
  for (const device of devices) {
675
- 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
+ }
676
722
  }
677
723
  });
678
724
  const addDevice = (device) => {
@@ -699,6 +745,57 @@ class ReactNativeBleTransport {
699
745
  }));
700
746
  });
701
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
+ }
702
799
  acquire(input) {
703
800
  var _a, _b;
704
801
  return __awaiter(this, void 0, void 0, function* () {
@@ -723,8 +820,8 @@ class ReactNativeBleTransport {
723
820
  if (forceCleanRunPromise && this.runPromise) {
724
821
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
725
822
  this.runPromise.reject(error);
726
- this.rejectAllProtocolV2Frames(error);
727
823
  this.runPromise = null;
824
+ this.runPromiseDeviceId = null;
728
825
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
729
826
  }
730
827
  const blePlxManager = yield this.getPlxManager();
@@ -757,14 +854,17 @@ class ReactNativeBleTransport {
757
854
  if (!device) {
758
855
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
759
856
  try {
760
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
857
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
761
858
  }
762
859
  catch (e) {
763
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
+ }
764
864
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
765
865
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
766
866
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
767
- device = yield blePlxManager.connectToDevice(uuid);
867
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
768
868
  }
769
869
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
770
870
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -780,23 +880,27 @@ class ReactNativeBleTransport {
780
880
  }
781
881
  if (!(yield device.isConnected())) {
782
882
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
883
+ const disconnectedDevice = device;
783
884
  try {
784
- device = yield device.connect(connectOptions);
885
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
785
886
  }
786
887
  catch (e) {
787
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
+ }
788
892
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
789
893
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
790
894
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
791
895
  try {
792
- device = yield device.connect();
896
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
793
897
  }
794
898
  catch (e) {
795
899
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
796
900
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
797
901
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
798
- yield device.cancelConnection();
799
- device = yield device.connect();
902
+ yield disconnectedDevice.cancelConnection();
903
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
800
904
  }
801
905
  }
802
906
  }
@@ -805,44 +909,35 @@ class ReactNativeBleTransport {
805
909
  }
806
910
  }
807
911
  }
808
- device = yield requestAndroidMtu(device);
809
- 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);
810
915
  const protocolHint = expectedProtocol
811
916
  ? undefined
812
- : (_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));
813
918
  yield this.release(uuid, true);
814
919
  if (protocolHint) {
815
920
  this.deviceProtocolHints.set(uuid, protocolHint);
816
921
  }
817
- const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
818
- if (reactNative.Platform.OS === 'android') {
819
- transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
820
- }
821
- const monitorToken = this.nextMonitorToken;
822
- this.nextMonitorToken += 1;
823
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
824
- transport$1.monitorToken = monitorToken;
825
- transport$1.notifyTransactionId = notifyTransactionId;
826
- this.monitorTokens.set(uuid, monitorToken);
827
- transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
828
- transportCache[uuid] = transport$1;
829
- this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
830
- if (reactNative.Platform.OS === 'ios') {
831
- yield new Promise(resolve => {
832
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
833
- });
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 };
834
936
  }
835
- else if (reactNative.Platform.OS === 'android') {
836
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
937
+ catch (error) {
938
+ yield this.release(uuid, true);
939
+ throw error;
837
940
  }
838
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
839
- (_b = this.emitter) === null || _b === void 0 ? void 0 : _b.emit('device-connect', {
840
- name: device.name,
841
- id: device.id,
842
- connectId: device.id,
843
- });
844
- this.attachDisconnectSubscription(transport$1, device, uuid);
845
- return { uuid, protocolType };
846
941
  });
847
942
  }
848
943
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
@@ -861,7 +956,7 @@ class ReactNativeBleTransport {
861
956
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
862
957
  return;
863
958
  }
864
- if (this.deviceProtocol.get(uuid) === 'V2') {
959
+ if (this.getActiveProtocol(uuid) === 'V2') {
865
960
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
866
961
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
867
962
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -879,7 +974,7 @@ class ReactNativeBleTransport {
879
974
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
880
975
  return;
881
976
  }
882
- if (this.runPromise) {
977
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
883
978
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
884
979
  if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
885
980
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -894,13 +989,11 @@ class ReactNativeBleTransport {
894
989
  ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
895
990
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
896
991
  this.runPromise.reject(notifyError);
897
- this.rejectAllProtocolV2Frames(notifyError);
898
992
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
899
993
  return;
900
994
  }
901
995
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
902
996
  this.runPromise.reject(notifyError);
903
- this.rejectAllProtocolV2Frames(notifyError);
904
997
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
905
998
  }
906
999
  return;
@@ -914,7 +1007,7 @@ class ReactNativeBleTransport {
914
1007
  }
915
1008
  try {
916
1009
  const data = buffer.Buffer.from(c.value, 'base64');
917
- const protocol = this.deviceProtocol.get(uuid);
1010
+ const protocol = this.getActiveProtocol(uuid);
918
1011
  if (!protocol) {
919
1012
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
920
1013
  return;
@@ -934,16 +1027,18 @@ class ReactNativeBleTransport {
934
1027
  const value = buffer.Buffer.from(buffer$1);
935
1028
  bufferLength = 0;
936
1029
  buffer$1 = [];
937
- (_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
+ }
938
1033
  }
939
1034
  }
940
1035
  catch (error) {
941
1036
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
942
1037
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
943
- if (this.deviceProtocol.get(uuid) === 'V2') {
1038
+ if (this.getActiveProtocol(uuid) === 'V2') {
944
1039
  this.rejectProtocolV2Frames(uuid, notifyError);
945
1040
  }
946
- else {
1041
+ else if (this.runPromiseDeviceId === uuid) {
947
1042
  (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
948
1043
  }
949
1044
  }
@@ -951,15 +1046,21 @@ class ReactNativeBleTransport {
951
1046
  return subscription;
952
1047
  }
953
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) {
954
1055
  var _a, _b, _c, _d, _e, _f, _g;
955
1056
  return __awaiter(this, void 0, void 0, function* () {
956
1057
  const transport = transportCache[uuid];
957
- yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
958
- if (this.runPromise) {
1058
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
959
1059
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
960
1060
  this.runPromise.reject(error);
961
1061
  this.runPromise = null;
962
- this.rejectAllProtocolV2Frames(error);
1062
+ this.runPromiseDeviceId = null;
1063
+ this.rejectProtocolV2Frames(uuid, error);
963
1064
  }
964
1065
  else {
965
1066
  this.resetProtocolV2Frames(uuid);
@@ -969,6 +1070,7 @@ class ReactNativeBleTransport {
969
1070
  this.resetProtocolV2Frames(uuid);
970
1071
  return Promise.resolve(true);
971
1072
  }
1073
+ yield this.restoreAndroidConnectionPriority(uuid, transport);
972
1074
  if (transport) {
973
1075
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
974
1076
  this.monitorTokens.delete(uuid);
@@ -989,7 +1091,9 @@ class ReactNativeBleTransport {
989
1091
  }
990
1092
  delete transportCache[uuid];
991
1093
  }
1094
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
992
1095
  this.deviceProtocol.delete(uuid);
1096
+ this.probingProtocols.delete(uuid);
993
1097
  (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
994
1098
  this.protocolV2Assemblers.delete(uuid);
995
1099
  this.resetProtocolV2Frames(uuid);
@@ -1019,7 +1123,6 @@ class ReactNativeBleTransport {
1019
1123
  if (!protocol) {
1020
1124
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1021
1125
  }
1022
- Log === null || Log === void 0 ? void 0 : Log.debug('transport call', createTransportCallLog(name, protocol, data));
1023
1126
  if (protocol === 'V2') {
1024
1127
  return this.callProtocolV2(uuid, name, data, options);
1025
1128
  }
@@ -1038,7 +1141,19 @@ class ReactNativeBleTransport {
1038
1141
  const transport = this.getCachedTransport(uuid);
1039
1142
  const runPromise = hdShared.createDeferred();
1040
1143
  runPromise.promise.catch(() => undefined);
1144
+ const supersededRunPromise = this.runPromise;
1145
+ if (supersededRunPromise) {
1146
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1147
+ }
1041
1148
  this.runPromise = runPromise;
1149
+ this.runPromiseDeviceId = uuid;
1150
+ const releaseOwnershipIfCurrent = () => {
1151
+ if (this.runPromise === runPromise) {
1152
+ this.runPromise = null;
1153
+ this.runPromiseDeviceId = null;
1154
+ }
1155
+ };
1156
+ const isCurrentOwner = () => this.runPromise === runPromise;
1042
1157
  const messages = this._messages;
1043
1158
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1044
1159
  let timeout;
@@ -1059,6 +1174,9 @@ class ReactNativeBleTransport {
1059
1174
  }
1060
1175
  catch (e) {
1061
1176
  onError(e);
1177
+ if (isWedgedWriteError(e)) {
1178
+ throw e;
1179
+ }
1062
1180
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1063
1181
  }
1064
1182
  }
@@ -1086,6 +1204,9 @@ class ReactNativeBleTransport {
1086
1204
  }
1087
1205
  catch (e) {
1088
1206
  onError(e);
1207
+ if (isWedgedWriteError(e)) {
1208
+ throw e;
1209
+ }
1089
1210
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1090
1211
  }
1091
1212
  }
@@ -1096,8 +1217,8 @@ class ReactNativeBleTransport {
1096
1217
  });
1097
1218
  }
1098
1219
  if (name === 'EmmcFileWrite') {
1099
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1100
- this.runPromise = null;
1220
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1221
+ releaseOwnershipIfCurrent();
1101
1222
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1102
1223
  });
1103
1224
  }
@@ -1113,7 +1234,7 @@ class ReactNativeBleTransport {
1113
1234
  let attempt = 0;
1114
1235
  while (true) {
1115
1236
  try {
1116
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1237
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1117
1238
  return;
1118
1239
  }
1119
1240
  catch (error) {
@@ -1121,36 +1242,18 @@ class ReactNativeBleTransport {
1121
1242
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1122
1243
  throw error;
1123
1244
  }
1124
- const shouldReconnect = retryType === 'reconnectable';
1125
- const delayMs = shouldReconnect
1126
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1127
- : resolveFirmwareUploadRetryDelay(attempt);
1245
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1128
1246
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1129
1247
  attempt: attempt + 1,
1130
1248
  delayMs,
1131
- reconnect: shouldReconnect,
1132
1249
  error,
1133
1250
  });
1134
- if (shouldReconnect) {
1135
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1136
- }
1137
1251
  yield delay(delayMs);
1138
1252
  attempt += 1;
1139
- if (shouldReconnect) {
1140
- try {
1141
- yield this.reconnectFirmwareUploadTransport(uuid, transport);
1142
- }
1143
- catch (e) {
1144
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
1145
- if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1146
- throw e;
1147
- }
1148
- }
1149
- }
1150
1253
  }
1151
1254
  }
1152
1255
  }), e => {
1153
- this.runPromise = null;
1256
+ releaseOwnershipIfCurrent();
1154
1257
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1155
1258
  });
1156
1259
  }
@@ -1158,11 +1261,17 @@ class ReactNativeBleTransport {
1158
1261
  for (const o of buffers) {
1159
1262
  const outData = o.toString('base64');
1160
1263
  try {
1161
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1264
+ const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1265
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1266
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1267
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1162
1268
  }
1163
1269
  catch (e) {
1164
1270
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1165
- this.runPromise = null;
1271
+ releaseOwnershipIfCurrent();
1272
+ if (isWedgedWriteError(e)) {
1273
+ throw e;
1274
+ }
1166
1275
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1167
1276
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1168
1277
  }
@@ -1195,12 +1304,19 @@ class ReactNativeBleTransport {
1195
1304
  return check.call(jsonData);
1196
1305
  }
1197
1306
  catch (e) {
1198
- if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1199
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
1307
+ if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1308
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1200
1309
  }
1201
1310
  else {
1202
1311
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1203
1312
  }
1313
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1314
+ const isStaleCall = this.runPromise !== runPromise;
1315
+ if (!isProbeTimeout &&
1316
+ !isStaleCall &&
1317
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1318
+ yield this.disconnect(uuid);
1319
+ }
1204
1320
  throw e;
1205
1321
  }
1206
1322
  finally {
@@ -1208,6 +1324,7 @@ class ReactNativeBleTransport {
1208
1324
  clearTimeout(timeout);
1209
1325
  if (this.runPromise === runPromise) {
1210
1326
  this.runPromise = null;
1327
+ this.runPromiseDeviceId = null;
1211
1328
  }
1212
1329
  }
1213
1330
  });
@@ -1220,6 +1337,7 @@ class ReactNativeBleTransport {
1220
1337
  return __awaiter(this, void 0, void 0, function* () {
1221
1338
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1222
1339
  const transport = transportCache[session];
1340
+ const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
1223
1341
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1224
1342
  try {
1225
1343
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1232,7 +1350,7 @@ class ReactNativeBleTransport {
1232
1350
  }
1233
1351
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1234
1352
  try {
1235
- 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);
1353
+ 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);
1236
1354
  transport.notifySubscription.remove();
1237
1355
  transport.notifySubscription = undefined;
1238
1356
  }
@@ -1242,7 +1360,7 @@ class ReactNativeBleTransport {
1242
1360
  }
1243
1361
  if (session) {
1244
1362
  try {
1245
- yield ((_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelTransaction(session));
1363
+ yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(session));
1246
1364
  }
1247
1365
  catch (e) {
1248
1366
  Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -1257,7 +1375,7 @@ class ReactNativeBleTransport {
1257
1375
  }
1258
1376
  }
1259
1377
  try {
1260
- yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelDeviceConnection(session));
1378
+ yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelDeviceConnection(session));
1261
1379
  }
1262
1380
  catch (e) {
1263
1381
  Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: manager.cancelDeviceConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -1266,19 +1384,21 @@ class ReactNativeBleTransport {
1266
1384
  delete transportCache[session];
1267
1385
  }
1268
1386
  this.deviceProtocol.delete(session);
1387
+ this.probingProtocols.delete(session);
1269
1388
  this.deviceProtocolHints.delete(session);
1389
+ this.sessionProtocols.delete(session);
1390
+ this.protocolReprobeFailures.delete(session);
1270
1391
  this.protocolV2Assemblers.delete(session);
1271
1392
  this.resetProtocolV2Frames(session);
1272
1393
  try {
1273
- (_d = this.emitter) === null || _d === void 0 ? void 0 : _d.emit('device-disconnect', {
1274
- name: (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name,
1275
- id: session,
1276
- connectId: session,
1277
- });
1394
+ this.emitDeviceDisconnect(session, (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name, monitorToken);
1278
1395
  }
1279
1396
  catch (e) {
1280
1397
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1281
1398
  }
1399
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1400
+ this.monitorTokens.delete(session);
1401
+ }
1282
1402
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1283
1403
  });
1284
1404
  }
@@ -1286,6 +1406,92 @@ class ReactNativeBleTransport {
1286
1406
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1287
1407
  if (this.runPromise) ;
1288
1408
  this.runPromise = null;
1409
+ this.runPromiseDeviceId = null;
1410
+ }
1411
+ connectWithTimeout(uuid, connect) {
1412
+ return __awaiter(this, void 0, void 0, function* () {
1413
+ let timer;
1414
+ let timedOut = false;
1415
+ const pending = connect();
1416
+ pending.catch(() => undefined);
1417
+ try {
1418
+ const result = yield Promise.race([
1419
+ pending,
1420
+ new Promise((_, reject) => {
1421
+ timer = setTimeout(() => {
1422
+ timedOut = true;
1423
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1424
+ }, BLE_CONNECT_TIMEOUT_MS);
1425
+ }),
1426
+ ]);
1427
+ return result;
1428
+ }
1429
+ catch (error) {
1430
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1431
+ this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1432
+ }
1433
+ throw error;
1434
+ }
1435
+ finally {
1436
+ if (timer)
1437
+ clearTimeout(timer);
1438
+ }
1439
+ });
1440
+ }
1441
+ resolveCharacteristicsWithTimeout(uuid, device) {
1442
+ return __awaiter(this, void 0, void 0, function* () {
1443
+ let timer;
1444
+ let timedOut = false;
1445
+ const pending = this.resolveCharacteristics(device);
1446
+ pending.catch(() => undefined);
1447
+ try {
1448
+ const result = yield Promise.race([
1449
+ pending,
1450
+ new Promise((_, reject) => {
1451
+ timer = setTimeout(() => {
1452
+ timedOut = true;
1453
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1454
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1455
+ }),
1456
+ ]);
1457
+ this.connectionSetupTimeoutCounts.delete(uuid);
1458
+ return result;
1459
+ }
1460
+ catch (error) {
1461
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1462
+ this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1463
+ }
1464
+ throw error;
1465
+ }
1466
+ finally {
1467
+ if (timer)
1468
+ clearTimeout(timer);
1469
+ }
1470
+ });
1471
+ }
1472
+ abandonStalledConnection(uuid, stage) {
1473
+ var _a, _b;
1474
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1475
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1476
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1477
+ stage,
1478
+ setupTimeoutsSinceSuccess: timeouts,
1479
+ });
1480
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1481
+ });
1482
+ const stalled = transportCache[uuid];
1483
+ if (stalled) {
1484
+ delete transportCache[uuid];
1485
+ }
1486
+ this.deviceProtocol.delete(uuid);
1487
+ this.probingProtocols.delete(uuid);
1488
+ this.protocolV2Assemblers.delete(uuid);
1489
+ this.resetProtocolV2Frames(uuid);
1490
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1491
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1492
+ this.resetPlxManager();
1493
+ this.connectionSetupTimeoutCounts.delete(uuid);
1494
+ }
1289
1495
  }
1290
1496
  getCachedTransport(uuid) {
1291
1497
  const transport = transportCache[uuid];
@@ -1294,22 +1500,118 @@ class ReactNativeBleTransport {
1294
1500
  }
1295
1501
  return transport;
1296
1502
  }
1503
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1504
+ return __awaiter(this, void 0, void 0, function* () {
1505
+ let timer;
1506
+ let timedOut = false;
1507
+ try {
1508
+ yield Promise.race([
1509
+ write(data),
1510
+ new Promise((_, reject) => {
1511
+ timer = setTimeout(() => {
1512
+ timedOut = true;
1513
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1514
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1515
+ }),
1516
+ ]);
1517
+ this.writeTimeoutCounts.delete(uuid);
1518
+ }
1519
+ catch (error) {
1520
+ if (timedOut) {
1521
+ if (isCurrentOwner && !isCurrentOwner()) {
1522
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1523
+ }
1524
+ else {
1525
+ this.tearDownWedgedLink(uuid);
1526
+ }
1527
+ }
1528
+ throw error;
1529
+ }
1530
+ finally {
1531
+ if (timer)
1532
+ clearTimeout(timer);
1533
+ }
1534
+ });
1535
+ }
1536
+ tearDownWedgedLink(uuid) {
1537
+ var _a;
1538
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1539
+ this.writeTimeoutCounts.set(uuid, timeouts);
1540
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1541
+ consecutiveWriteTimeouts: timeouts,
1542
+ });
1543
+ const wedged = transportCache[uuid];
1544
+ this.disconnect(uuid).catch(error => {
1545
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1546
+ });
1547
+ if (wedged && transportCache[uuid] === wedged) {
1548
+ delete transportCache[uuid];
1549
+ }
1550
+ this.deviceProtocol.delete(uuid);
1551
+ this.probingProtocols.delete(uuid);
1552
+ this.protocolV2Assemblers.delete(uuid);
1553
+ this.resetProtocolV2Frames(uuid);
1554
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1555
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1556
+ this.resetPlxManager();
1557
+ this.writeTimeoutCounts.delete(uuid);
1558
+ }
1559
+ }
1560
+ resetPlxManager() {
1561
+ const manager = this.blePlxManager;
1562
+ this.blePlxManager = undefined;
1563
+ Object.keys(transportCache).forEach(key => {
1564
+ delete transportCache[key];
1565
+ });
1566
+ this.deviceProtocol.clear();
1567
+ this.probingProtocols.clear();
1568
+ this.sessionProtocols.clear();
1569
+ this.protocolReprobeFailures.clear();
1570
+ this.writeTimeoutCounts.clear();
1571
+ this.connectionSetupTimeoutCounts.clear();
1572
+ this.monitorTokens.clear();
1573
+ this.protocolV2Assemblers.clear();
1574
+ try {
1575
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1576
+ }
1577
+ catch (error) {
1578
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1579
+ }
1580
+ }
1297
1581
  createProtocolMismatchError(expected) {
1298
1582
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1299
1583
  }
1300
1584
  createProtocolDetectionError() {
1301
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1585
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1302
1586
  }
1303
1587
  clearProbeProtocol(uuid, protocol) {
1588
+ if (this.probingProtocols.get(uuid) === protocol) {
1589
+ this.probingProtocols.delete(uuid);
1590
+ }
1304
1591
  if (this.deviceProtocol.get(uuid) === protocol) {
1305
1592
  this.deviceProtocol.delete(uuid);
1306
1593
  }
1307
1594
  }
1308
- detectProtocol(uuid, expectedProtocol, protocolHint) {
1595
+ getActiveProtocol(uuid) {
1596
+ var _a;
1597
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1598
+ }
1599
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1600
+ var _a;
1309
1601
  return __awaiter(this, void 0, void 0, function* () {
1602
+ if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
1603
+ this.deviceProtocol.set(uuid, expectedProtocol);
1604
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1605
+ deviceId: uuid,
1606
+ protocol: expectedProtocol,
1607
+ source: 'expected',
1608
+ });
1609
+ return expectedProtocol;
1610
+ }
1310
1611
  if (expectedProtocol === 'V1') {
1311
1612
  if (yield this.probeProtocolV1(uuid)) {
1312
1613
  this.deviceProtocol.set(uuid, 'V1');
1614
+ this.sessionProtocols.set(uuid, 'V1');
1313
1615
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1314
1616
  deviceId: uuid,
1315
1617
  protocol: 'V1',
@@ -1320,23 +1622,41 @@ class ReactNativeBleTransport {
1320
1622
  throw this.createProtocolMismatchError(expectedProtocol);
1321
1623
  }
1322
1624
  if (expectedProtocol === 'V2') {
1323
- this.deviceProtocol.set(uuid, 'V2');
1324
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1325
- deviceId: uuid,
1326
- protocol: 'V2',
1327
- source: 'expected',
1328
- });
1329
- return 'V2';
1625
+ if (yield this.probeProtocolV2(uuid)) {
1626
+ this.deviceProtocol.set(uuid, 'V2');
1627
+ this.sessionProtocols.set(uuid, 'V2');
1628
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1629
+ deviceId: uuid,
1630
+ protocol: 'V2',
1631
+ source: 'expected',
1632
+ });
1633
+ return 'V2';
1634
+ }
1635
+ throw this.createProtocolMismatchError(expectedProtocol);
1330
1636
  }
1331
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1637
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1638
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1639
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1640
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1641
+ !protocolHint &&
1642
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1643
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1332
1644
  for (let i = 0; i < probeOrder.length; i += 1) {
1333
1645
  const protocol = probeOrder[i];
1334
1646
  if (i > 0) {
1335
1647
  yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1648
+ if (!transportCache[uuid]) {
1649
+ if (!rebuildTransport) {
1650
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1651
+ }
1652
+ yield rebuildTransport();
1653
+ }
1336
1654
  }
1337
1655
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1338
1656
  if (detected) {
1339
1657
  this.deviceProtocol.set(uuid, protocol);
1658
+ this.sessionProtocols.set(uuid, protocol);
1659
+ this.protocolReprobeFailures.delete(uuid);
1340
1660
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1341
1661
  deviceId: uuid,
1342
1662
  protocol,
@@ -1345,7 +1665,14 @@ class ReactNativeBleTransport {
1345
1665
  return protocol;
1346
1666
  }
1347
1667
  }
1668
+ if (trustSessionProtocol) {
1669
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1670
+ }
1671
+ else {
1672
+ this.protocolReprobeFailures.delete(uuid);
1673
+ }
1348
1674
  this.deviceProtocol.delete(uuid);
1675
+ this.probingProtocols.delete(uuid);
1349
1676
  throw this.createProtocolDetectionError();
1350
1677
  });
1351
1678
  }
@@ -1397,13 +1724,17 @@ class ReactNativeBleTransport {
1397
1724
  return false;
1398
1725
  }
1399
1726
  try {
1400
- this.deviceProtocol.set(uuid, 'V1');
1401
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1727
+ this.probingProtocols.set(uuid, 'V1');
1728
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1729
+ this.probingProtocols.delete(uuid);
1402
1730
  return true;
1403
1731
  }
1404
1732
  catch (error) {
1405
1733
  this.clearProbeProtocol(uuid, 'V1');
1406
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1734
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1735
+ if (isWedgedWriteError(error)) {
1736
+ throw error;
1737
+ }
1407
1738
  return false;
1408
1739
  }
1409
1740
  });
@@ -1414,7 +1745,7 @@ class ReactNativeBleTransport {
1414
1745
  if (!this._messages || !this._messagesV2) {
1415
1746
  return false;
1416
1747
  }
1417
- this.deviceProtocol.set(uuid, 'V2');
1748
+ this.probingProtocols.set(uuid, 'V2');
1418
1749
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1419
1750
  const detected = yield transport.probeProtocolV2({
1420
1751
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1430,6 +1761,9 @@ class ReactNativeBleTransport {
1430
1761
  if (!detected) {
1431
1762
  this.clearProbeProtocol(uuid, 'V2');
1432
1763
  }
1764
+ else {
1765
+ this.probingProtocols.delete(uuid);
1766
+ }
1433
1767
  return detected;
1434
1768
  });
1435
1769
  }
@@ -1472,16 +1806,8 @@ class ReactNativeBleTransport {
1472
1806
  }
1473
1807
  this.getProtocolV2FrameQueue(uuid).push(frame);
1474
1808
  }
1475
- rejectAllProtocolV2Frames(error) {
1476
- this.protocolV2FrameQueues.clear();
1477
- for (const framePromise of this.protocolV2FramePromises.values()) {
1478
- framePromise.reject(error);
1479
- }
1480
- this.protocolV2FramePromises.clear();
1481
- }
1482
1809
  resetProtocolV2Frames(uuid) {
1483
- this.protocolV2FrameQueues.delete(uuid);
1484
- this.protocolV2FramePromises.delete(uuid);
1810
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1485
1811
  }
1486
1812
  rejectProtocolV2Frames(uuid, error) {
1487
1813
  this.protocolV2FrameQueues.delete(uuid);
@@ -1509,20 +1835,70 @@ class ReactNativeBleTransport {
1509
1835
  }
1510
1836
  });
1511
1837
  }
1512
- writeProtocolV2Frame(transport, frame) {
1838
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1839
+ return __awaiter(this, void 0, void 0, function* () {
1840
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
1841
+ platform: reactNative.Platform.OS,
1842
+ highThroughput: context.highThroughput,
1843
+ requestedWithResponse: context.writeWithResponse,
1844
+ characteristic: transport.writeCharacteristic,
1845
+ });
1846
+ let attempt = 0;
1847
+ for (;;) {
1848
+ assertCurrentGeneration();
1849
+ if (context.signal.aborted) {
1850
+ throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1851
+ }
1852
+ try {
1853
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
1854
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1855
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1856
+ try {
1857
+ assertCurrentGeneration();
1858
+ return !context.signal.aborted;
1859
+ }
1860
+ catch (_a) {
1861
+ return false;
1862
+ }
1863
+ });
1864
+ assertCurrentGeneration();
1865
+ return;
1866
+ }
1867
+ catch (error) {
1868
+ if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
1869
+ attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1870
+ throw error;
1871
+ }
1872
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1873
+ attempt += 1;
1874
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
1875
+ name: context.messageName,
1876
+ attempt,
1877
+ delayMs,
1878
+ });
1879
+ yield delay(delayMs);
1880
+ }
1881
+ }
1882
+ });
1883
+ }
1884
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1513
1885
  return __awaiter(this, void 0, void 0, function* () {
1514
1886
  const tuning = getProtocolV2BleTuning();
1515
1887
  const packetCapacity = resolveProtocolV2PacketCapacity({
1516
1888
  platform: reactNative.Platform.OS,
1517
1889
  iosPacketLength: tuning.iosPacketLength,
1518
1890
  androidPacketLength: tuning.androidPacketLength,
1519
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
1891
+ mtu: transport$1.mtuSize,
1892
+ });
1893
+ yield transport.writeProtocolV2BleFrame({
1894
+ frame,
1895
+ packetCapacity,
1896
+ assertActive: assertCurrentGeneration,
1897
+ signal: context.signal,
1898
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1899
+ wait: delay,
1900
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1520
1901
  });
1521
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1522
- const chunk = frame.slice(offset, offset + packetCapacity);
1523
- const base64 = buffer.Buffer.from(chunk).toString('base64');
1524
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1525
- }
1526
1902
  });
1527
1903
  }
1528
1904
  callProtocolV2(uuid, name, data, options) {
@@ -1531,15 +1907,40 @@ class ReactNativeBleTransport {
1531
1907
  if (!this._messages || !this._messagesV2) {
1532
1908
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1533
1909
  }
1534
- 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 });
1535
- const highVolumeWrite = transport.LogBlockCommand.has(name);
1536
- if (highVolumeWrite) {
1910
+ const callOptions = options;
1911
+ const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
1912
+ if (highThroughputWrite) {
1913
+ yield this.ensureProtocolV2HighThroughputMtu(uuid);
1537
1914
  const tuning = getProtocolV2BleTuning();
1538
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1539
- name,
1540
- writeMode: 'withoutResponse',
1541
- packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1915
+ const currentTransport = this.getCachedTransport(uuid);
1916
+ const writeWithResponse = shouldWriteProtocolV2WithResponse({
1917
+ platform: reactNative.Platform.OS,
1918
+ highThroughput: true,
1919
+ requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
1920
+ characteristic: currentTransport.writeCharacteristic,
1921
+ });
1922
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1923
+ platform: reactNative.Platform.OS,
1924
+ iosPacketLength: tuning.iosPacketLength,
1925
+ androidPacketLength: tuning.androidPacketLength,
1926
+ mtu: currentTransport.mtuSize,
1542
1927
  });
1928
+ const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
1929
+ const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
1930
+ const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
1931
+ if (!loggedSignatures.has(logSignature)) {
1932
+ loggedSignatures.add(logSignature);
1933
+ this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
1934
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1935
+ name,
1936
+ writeMode,
1937
+ reportedMtu: currentTransport.mtuSize,
1938
+ packetCapacity,
1939
+ });
1940
+ }
1941
+ }
1942
+ if (highThroughputWrite) {
1943
+ yield this.enableAndroidHighConnectionPriority(uuid);
1543
1944
  }
1544
1945
  try {
1545
1946
  return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
@@ -1548,6 +1949,85 @@ class ReactNativeBleTransport {
1548
1949
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1549
1950
  throw e;
1550
1951
  }
1952
+ finally {
1953
+ if (highThroughputWrite) {
1954
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
1955
+ }
1956
+ }
1957
+ });
1958
+ }
1959
+ ensureProtocolV2HighThroughputMtu(uuid) {
1960
+ return __awaiter(this, void 0, void 0, function* () {
1961
+ const transport = this.getCachedTransport(uuid);
1962
+ if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
1963
+ return;
1964
+ const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
1965
+ transport.device = refreshedDevice;
1966
+ transport.mtuSize =
1967
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
1968
+ if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
1969
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`);
1970
+ }
1971
+ });
1972
+ }
1973
+ clearAndroidPriorityResetTimer(uuid) {
1974
+ const timerId = this.androidPriorityResetTimers.get(uuid);
1975
+ if (timerId !== undefined) {
1976
+ clearTimeout(timerId);
1977
+ this.androidPriorityResetTimers.delete(uuid);
1978
+ }
1979
+ }
1980
+ enableAndroidHighConnectionPriority(uuid) {
1981
+ return __awaiter(this, void 0, void 0, function* () {
1982
+ if (reactNative.Platform.OS !== 'android')
1983
+ return;
1984
+ this.clearAndroidPriorityResetTimer(uuid);
1985
+ if (this.androidHighPriorityDevices.has(uuid))
1986
+ return;
1987
+ const transport = transportCache[uuid];
1988
+ if (!transport)
1989
+ return;
1990
+ try {
1991
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
1992
+ this.androidHighPriorityDevices.add(uuid);
1993
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
1994
+ priority: 'high',
1995
+ });
1996
+ }
1997
+ catch (error) {
1998
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
1999
+ error: error instanceof Error ? error.message : String(error),
2000
+ });
2001
+ }
2002
+ });
2003
+ }
2004
+ scheduleAndroidBalancedConnectionPriority(uuid) {
2005
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
2006
+ return;
2007
+ this.clearAndroidPriorityResetTimer(uuid);
2008
+ const timerId = setTimeout(() => {
2009
+ this.androidPriorityResetTimers.delete(uuid);
2010
+ this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
2011
+ }, ANDROID_HIGH_PRIORITY_IDLE_MS);
2012
+ this.androidPriorityResetTimers.set(uuid, timerId);
2013
+ }
2014
+ restoreAndroidConnectionPriority(uuid, transport) {
2015
+ return __awaiter(this, void 0, void 0, function* () {
2016
+ this.clearAndroidPriorityResetTimer(uuid);
2017
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
2018
+ return;
2019
+ }
2020
+ try {
2021
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
2022
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2023
+ priority: 'balanced',
2024
+ });
2025
+ }
2026
+ catch (error) {
2027
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
2028
+ error: error instanceof Error ? error.message : String(error),
2029
+ });
2030
+ }
1551
2031
  });
1552
2032
  }
1553
2033
  createProtocolV2Adapter(uuid) {
@@ -1568,10 +2048,10 @@ class ReactNativeBleTransport {
1568
2048
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1569
2049
  this.resetProtocolV2Frames(uuid);
1570
2050
  },
1571
- writeFrame: (frame) => __awaiter(this, void 0, void 0, function* () {
2051
+ writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1572
2052
  assertCurrentGeneration();
1573
2053
  const currentTransport = this.getCachedTransport(uuid);
1574
- yield this.writeProtocolV2Frame(currentTransport, frame);
2054
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1575
2055
  }),
1576
2056
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1577
2057
  assertCurrentGeneration();
@@ -1592,11 +2072,18 @@ class ReactNativeBleTransport {
1592
2072
  };
1593
2073
  }
1594
2074
  getProtocolType(path) {
1595
- return this.deviceProtocol.get(path);
2075
+ return this.getActiveProtocol(path);
1596
2076
  }
1597
2077
  }
1598
2078
 
2079
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2080
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2081
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2082
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2083
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2084
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1599
2085
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1600
2086
  exports["default"] = ReactNativeBleTransport;
2087
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1601
2088
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1602
2089
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;