@onekeyfe/hd-transport-react-native 1.2.0-alpha.1 → 1.2.0-alpha.100

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,15 +395,44 @@ 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();
417
- this.activeProtocolV2Call = null;
418
- this.nextProtocolV2CallToken = 1;
410
+ this.protocolV2Links = new transport.ProtocolV2LinkManager({
411
+ getSchemas: () => {
412
+ if (!this._messages || !this._messagesV2) {
413
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
414
+ }
415
+ return {
416
+ protocolV1: this._messages,
417
+ protocolV2: this._messagesV2,
418
+ };
419
+ },
420
+ classifyError: () => 'link-fatal',
421
+ onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
422
+ var _b;
423
+ (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
424
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
425
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
426
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
427
+ yield this.releaseNative(uuid, true);
428
+ }
429
+ }),
430
+ });
419
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();
420
436
  this.nextMonitorToken = 1;
421
437
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
422
438
  }
@@ -430,8 +446,18 @@ class ReactNativeBleTransport {
430
446
  this._messages = messages;
431
447
  }
432
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;
433
454
  this._messagesV2 = parseConfigure(signedData);
434
- 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
+ }
435
461
  }
436
462
  listen() {
437
463
  }
@@ -442,7 +468,6 @@ class ReactNativeBleTransport {
442
468
  return Promise.resolve(this.blePlxManager);
443
469
  }
444
470
  resolveCharacteristics(device) {
445
- var _a, _b, _c, _d;
446
471
  return __awaiter(this, void 0, void 0, function* () {
447
472
  yield device.discoverAllServicesAndCharacteristics();
448
473
  let infos = tryToGetConfiguration(device);
@@ -459,19 +484,11 @@ class ReactNativeBleTransport {
459
484
  }
460
485
  }
461
486
  }
462
- let fallbackServiceUuid;
463
487
  if (!infos) {
464
488
  const services = yield device.services();
465
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));
466
- const knownService = services.find(service => getInfosForServiceUuid(service.uuid, 'classic'));
467
- const fallbackService = (_a = knownService !== null && knownService !== void 0 ? knownService : services.find(service => !isGenericBleService(service.uuid))) !== null && _a !== void 0 ? _a : services[0];
468
- if (fallbackService) {
469
- fallbackServiceUuid = fallbackService.uuid;
470
- characteristics = yield device.characteristicsForService(fallbackService.uuid);
471
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
472
- }
473
490
  }
474
- if (!infos && !fallbackServiceUuid) {
491
+ if (!infos) {
475
492
  try {
476
493
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
477
494
  yield device.cancelConnection();
@@ -481,9 +498,7 @@ class ReactNativeBleTransport {
481
498
  }
482
499
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
483
500
  }
484
- const serviceUuid = (_b = infos === null || infos === void 0 ? void 0 : infos.serviceUuid) !== null && _b !== void 0 ? _b : fallbackServiceUuid;
485
- const writeUuid = (_c = infos === null || infos === void 0 ? void 0 : infos.writeUuid) !== null && _c !== void 0 ? _c : '00000002-0000-1000-8000-00805f9b34fb';
486
- 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;
487
502
  if (!serviceUuid) {
488
503
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
489
504
  }
@@ -524,8 +539,8 @@ class ReactNativeBleTransport {
524
539
  attachDisconnectSubscription(transport, device, uuid) {
525
540
  var _a;
526
541
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
542
+ const { monitorToken } = transport;
527
543
  transport.disconnectSubscription = device.onDisconnected(() => {
528
- var _a;
529
544
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
530
545
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
531
546
  return;
@@ -534,17 +549,16 @@ class ReactNativeBleTransport {
534
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);
535
550
  return;
536
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
+ }
537
556
  try {
538
557
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
539
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
540
- name: device === null || device === void 0 ? void 0 : device.name,
541
- id: device === null || device === void 0 ? void 0 : device.id,
542
- connectId: device === null || device === void 0 ? void 0 : device.id,
543
- });
544
- if (this.runPromise) {
558
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
559
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
545
560
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
546
561
  this.runPromise.reject(error);
547
- this.rejectAllProtocolV2Frames(error);
548
562
  }
549
563
  }
550
564
  catch (e) {
@@ -555,6 +569,22 @@ class ReactNativeBleTransport {
555
569
  }
556
570
  });
557
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
+ }
558
588
  reconnectFirmwareUploadTransport(uuid, transport) {
559
589
  var _a, _b;
560
590
  return __awaiter(this, void 0, void 0, function* () {
@@ -568,19 +598,19 @@ class ReactNativeBleTransport {
568
598
  const isConnected = yield device.isConnected().catch(() => false);
569
599
  if (!isConnected) {
570
600
  try {
571
- device = yield device.connect(connectOptions);
601
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
572
602
  }
573
603
  catch (e) {
574
604
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
575
605
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
576
- device = yield device.connect();
606
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
577
607
  }
578
608
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
579
609
  throw e;
580
610
  }
581
611
  }
582
612
  }
583
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
613
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
584
614
  transport.device = device;
585
615
  transport.writeCharacteristic = writeCharacteristic;
586
616
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -624,13 +654,12 @@ class ReactNativeBleTransport {
624
654
  return;
625
655
  }
626
656
  }
627
- blePlxManager.startDeviceScan(null, {
657
+ blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
628
658
  allowDuplicates: true,
629
659
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
630
660
  }, (error, device) => {
631
- var _a, _b, _c;
661
+ var _a;
632
662
  if (error) {
633
- Log === null || Log === void 0 ? void 0 : Log.debug('ble scan manager: ', blePlxManager);
634
663
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
635
664
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
636
665
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
@@ -650,25 +679,19 @@ class ReactNativeBleTransport {
650
679
  return;
651
680
  }
652
681
  const displayName = getDeviceDisplayName(device);
653
- 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) ||
654
- 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) ||
655
- hasKnownOneKeyService(device);
656
- const shouldTraceCandidate = !!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
657
- if (shouldTraceCandidate) {
658
- 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,
659
689
  name: device === null || device === void 0 ? void 0 : device.name,
660
690
  localName: device === null || device === void 0 ? void 0 : device.localName,
661
- id: device === null || device === void 0 ? void 0 : device.id,
662
- serviceUUIDs: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
663
- accepted: isOneKey,
691
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
664
692
  });
665
- }
666
693
  if (isOneKey) {
667
- Log === null || Log === void 0 ? void 0 : Log.debug('search device start ======================');
668
- const { name, localName, id, serviceUUIDs } = device !== null && device !== void 0 ? device : {};
669
- 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(',')}`);
670
694
  addDevice(device);
671
- Log === null || Log === void 0 ? void 0 : Log.debug('search device end ======================\n');
672
695
  }
673
696
  else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
674
697
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
@@ -679,10 +702,23 @@ class ReactNativeBleTransport {
679
702
  });
680
703
  }
681
704
  });
682
- getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
705
+ getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
683
706
  for (const device of devices) {
684
- Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
685
- 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
+ }
686
722
  }
687
723
  });
688
724
  const addDevice = (device) => {
@@ -694,6 +730,12 @@ class ReactNativeBleTransport {
694
730
  this.deviceProtocolHints.set(device.id, protocolHint);
695
731
  }
696
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
+ });
697
739
  }
698
740
  };
699
741
  timer.timeout(() => {
@@ -703,6 +745,57 @@ class ReactNativeBleTransport {
703
745
  }));
704
746
  });
705
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
+ }
706
799
  acquire(input) {
707
800
  var _a, _b;
708
801
  return __awaiter(this, void 0, void 0, function* () {
@@ -727,9 +820,8 @@ class ReactNativeBleTransport {
727
820
  if (forceCleanRunPromise && this.runPromise) {
728
821
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
729
822
  this.runPromise.reject(error);
730
- this.rejectAllProtocolV2Frames(error);
731
823
  this.runPromise = null;
732
- this.activeProtocolV2Call = null;
824
+ this.runPromiseDeviceId = null;
733
825
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
734
826
  }
735
827
  const blePlxManager = yield this.getPlxManager();
@@ -762,14 +854,17 @@ class ReactNativeBleTransport {
762
854
  if (!device) {
763
855
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
764
856
  try {
765
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
857
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
766
858
  }
767
859
  catch (e) {
768
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
+ }
769
864
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
770
865
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
771
866
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
772
- device = yield blePlxManager.connectToDevice(uuid);
867
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
773
868
  }
774
869
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
775
870
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -785,23 +880,27 @@ class ReactNativeBleTransport {
785
880
  }
786
881
  if (!(yield device.isConnected())) {
787
882
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
883
+ const disconnectedDevice = device;
788
884
  try {
789
- device = yield device.connect(connectOptions);
885
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
790
886
  }
791
887
  catch (e) {
792
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
+ }
793
892
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
794
893
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
795
894
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
796
895
  try {
797
- device = yield device.connect();
896
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
798
897
  }
799
898
  catch (e) {
800
899
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
801
900
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
802
901
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
803
- yield device.cancelConnection();
804
- device = yield device.connect();
902
+ yield disconnectedDevice.cancelConnection();
903
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
805
904
  }
806
905
  }
807
906
  }
@@ -810,51 +909,42 @@ class ReactNativeBleTransport {
810
909
  }
811
910
  }
812
911
  }
813
- device = yield requestAndroidMtu(device);
814
- 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);
815
915
  const protocolHint = expectedProtocol
816
916
  ? undefined
817
- : (_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));
818
918
  yield this.release(uuid, true);
819
919
  if (protocolHint) {
820
920
  this.deviceProtocolHints.set(uuid, protocolHint);
821
921
  }
822
- const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
823
- if (reactNative.Platform.OS === 'android') {
824
- transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
825
- }
826
- const monitorToken = this.nextMonitorToken;
827
- this.nextMonitorToken += 1;
828
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
829
- transport$1.monitorToken = monitorToken;
830
- transport$1.notifyTransactionId = notifyTransactionId;
831
- this.monitorTokens.set(uuid, monitorToken);
832
- transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
833
- transportCache[uuid] = transport$1;
834
- this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler());
835
- if (reactNative.Platform.OS === 'ios') {
836
- yield new Promise(resolve => {
837
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
838
- });
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 };
839
936
  }
840
- else if (reactNative.Platform.OS === 'android') {
841
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
937
+ catch (error) {
938
+ yield this.release(uuid, true);
939
+ throw error;
842
940
  }
843
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
844
- (_b = this.emitter) === null || _b === void 0 ? void 0 : _b.emit('device-connect', {
845
- name: device.name,
846
- id: device.id,
847
- connectId: device.id,
848
- });
849
- this.attachDisconnectSubscription(transport$1, device, uuid);
850
- return { uuid, protocolType };
851
941
  });
852
942
  }
853
943
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
854
944
  let bufferLength = 0;
855
945
  let buffer$1 = [];
856
946
  const subscription = characteristic.monitor((error, c) => {
857
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
947
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
858
948
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
859
949
  if (error) {
860
950
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -866,28 +956,44 @@ class ReactNativeBleTransport {
866
956
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
867
957
  return;
868
958
  }
869
- if (this.runPromise) {
870
- let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
959
+ if (this.getActiveProtocol(uuid) === 'V2') {
960
+ let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
871
961
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
872
- ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
962
+ errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
873
963
  }
874
- if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
875
- ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
964
+ else if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
965
+ errorCode = hdShared.HardwareErrorCode.BleDeviceBondError;
876
966
  }
877
- if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
967
+ else if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
878
968
  ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('Cannot find client characteristic config descriptor')) ||
879
969
  ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('The handle is invalid')) ||
880
970
  ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('Writing is not permitted')) ||
881
971
  ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('notify change failed for device'))) {
972
+ errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
973
+ }
974
+ this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
975
+ return;
976
+ }
977
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
978
+ let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
979
+ if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
980
+ ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
981
+ }
982
+ if ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Encryption is insufficient')) {
983
+ ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
984
+ }
985
+ if (((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('Cannot write client characteristic config descriptor')) ||
986
+ ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Cannot find client characteristic config descriptor')) ||
987
+ ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('The handle is invalid')) ||
988
+ ((_o = error.reason) === null || _o === void 0 ? void 0 : _o.includes('Writing is not permitted')) ||
989
+ ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
882
990
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
883
991
  this.runPromise.reject(notifyError);
884
- this.rejectAllProtocolV2Frames(notifyError);
885
992
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
886
993
  return;
887
994
  }
888
995
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
889
996
  this.runPromise.reject(notifyError);
890
- this.rejectAllProtocolV2Frames(notifyError);
891
997
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
892
998
  }
893
999
  return;
@@ -901,13 +1007,13 @@ class ReactNativeBleTransport {
901
1007
  }
902
1008
  try {
903
1009
  const data = buffer.Buffer.from(c.value, 'base64');
904
- const protocol = this.deviceProtocol.get(uuid);
1010
+ const protocol = this.getActiveProtocol(uuid);
905
1011
  if (!protocol) {
906
1012
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
907
1013
  return;
908
1014
  }
909
1015
  if (protocol === 'V2') {
910
- this.handleProtocolV2Notification(uuid, new Uint8Array(data));
1016
+ this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
911
1017
  return;
912
1018
  }
913
1019
  if (isHeaderChunk(data)) {
@@ -921,28 +1027,40 @@ class ReactNativeBleTransport {
921
1027
  const value = buffer.Buffer.from(buffer$1);
922
1028
  bufferLength = 0;
923
1029
  buffer$1 = [];
924
- (_h = this.runPromise) === null || _h === void 0 ? void 0 : _h.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
+ }
925
1033
  }
926
1034
  }
927
1035
  catch (error) {
928
1036
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
929
1037
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
930
- (_j = this.runPromise) === null || _j === void 0 ? void 0 : _j.reject(notifyError);
931
- this.rejectAllProtocolV2Frames(notifyError);
1038
+ if (this.getActiveProtocol(uuid) === 'V2') {
1039
+ this.rejectProtocolV2Frames(uuid, notifyError);
1040
+ }
1041
+ else if (this.runPromiseDeviceId === uuid) {
1042
+ (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
1043
+ }
932
1044
  }
933
1045
  }, notifyTransactionId);
934
1046
  return subscription;
935
1047
  }
936
1048
  release(uuid, onclose = false) {
937
- var _a, _b, _c, _d, _e, _f, _g, _h;
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) {
1055
+ var _a, _b, _c, _d, _e, _f, _g;
938
1056
  return __awaiter(this, void 0, void 0, function* () {
939
1057
  const transport = transportCache[uuid];
940
- if (this.runPromise) {
1058
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
941
1059
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
942
1060
  this.runPromise.reject(error);
943
1061
  this.runPromise = null;
944
- this.rejectAllProtocolV2Frames(error);
945
- this.activeProtocolV2Call = null;
1062
+ this.runPromiseDeviceId = null;
1063
+ this.rejectProtocolV2Frames(uuid, error);
946
1064
  }
947
1065
  else {
948
1066
  this.resetProtocolV2Frames(uuid);
@@ -950,24 +1068,22 @@ class ReactNativeBleTransport {
950
1068
  if (reactNative.Platform.OS === 'android' && !onclose && transport) {
951
1069
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
952
1070
  this.resetProtocolV2Frames(uuid);
953
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
954
- this.activeProtocolV2Call = null;
955
- }
956
1071
  return Promise.resolve(true);
957
1072
  }
1073
+ yield this.restoreAndroidConnectionPriority(uuid, transport);
958
1074
  if (transport) {
959
1075
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
960
1076
  this.monitorTokens.delete(uuid);
961
1077
  }
962
1078
  Log === null || Log === void 0 ? void 0 : Log.debug('release: removing disconnect subscription for device: ', uuid);
963
- (_c = transport.disconnectSubscription) === null || _c === void 0 ? void 0 : _c.remove();
1079
+ (_b = transport.disconnectSubscription) === null || _b === void 0 ? void 0 : _b.remove();
964
1080
  transport.disconnectSubscription = undefined;
965
- Log === null || Log === void 0 ? void 0 : Log.debug('release: removing notify subscription, characteristic: ', (_d = transport.notifyCharacteristic) === null || _d === void 0 ? void 0 : _d.uuid);
966
- (_e = transport.notifySubscription) === null || _e === void 0 ? void 0 : _e.remove();
1081
+ Log === null || Log === void 0 ? void 0 : Log.debug('release: removing notify subscription, characteristic: ', (_c = transport.notifyCharacteristic) === null || _c === void 0 ? void 0 : _c.uuid);
1082
+ (_d = transport.notifySubscription) === null || _d === void 0 ? void 0 : _d.remove();
967
1083
  transport.notifySubscription = undefined;
968
1084
  if (transport.notifyTransactionId) {
969
1085
  try {
970
- yield ((_f = this.blePlxManager) === null || _f === void 0 ? void 0 : _f.cancelTransaction(transport.notifyTransactionId));
1086
+ yield ((_e = this.blePlxManager) === null || _e === void 0 ? void 0 : _e.cancelTransaction(transport.notifyTransactionId));
971
1087
  }
972
1088
  catch (e) {
973
1089
  Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel notify transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -975,13 +1091,14 @@ class ReactNativeBleTransport {
975
1091
  }
976
1092
  delete transportCache[uuid];
977
1093
  }
1094
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
978
1095
  this.deviceProtocol.delete(uuid);
979
- this.deviceProtocolHints.delete(uuid);
980
- (_g = this.protocolV2Assemblers.get(uuid)) === null || _g === void 0 ? void 0 : _g.reset();
1096
+ this.probingProtocols.delete(uuid);
1097
+ (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
981
1098
  this.protocolV2Assemblers.delete(uuid);
982
1099
  this.resetProtocolV2Frames(uuid);
983
1100
  try {
984
- yield ((_h = this.blePlxManager) === null || _h === void 0 ? void 0 : _h.cancelTransaction(uuid));
1101
+ yield ((_g = this.blePlxManager) === null || _g === void 0 ? void 0 : _g.cancelTransaction(uuid));
985
1102
  }
986
1103
  catch (e) {
987
1104
  Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
@@ -1002,30 +1119,18 @@ class ReactNativeBleTransport {
1002
1119
  if (this._messages == null) {
1003
1120
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1004
1121
  }
1005
- const forceRun = name === 'Initialize' || name === 'Cancel';
1006
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native call this.runPromise', this.runPromise);
1007
- if (this.runPromise && !forceRun) {
1008
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1009
- }
1010
1122
  const protocol = this.getProtocolType(uuid);
1011
1123
  if (!protocol) {
1012
1124
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1013
1125
  }
1014
- if (name === 'ResourceUpdate' || name === 'ResourceAck') {
1015
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
1016
- file_name: data === null || data === void 0 ? void 0 : data.file_name,
1017
- hash: data === null || data === void 0 ? void 0 : data.hash,
1018
- });
1019
- }
1020
- else if (transport.LogBlockCommand.has(name)) {
1021
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
1022
- }
1023
- else {
1024
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
1025
- }
1126
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
1026
1127
  if (protocol === 'V2') {
1027
1128
  return this.callProtocolV2(uuid, name, data, options);
1028
1129
  }
1130
+ const forceRun = name === 'Initialize' || name === 'Cancel';
1131
+ if (this.runPromise && !forceRun) {
1132
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1133
+ }
1029
1134
  return this.callProtocolV1(uuid, name, data, options);
1030
1135
  });
1031
1136
  }
@@ -1037,7 +1142,19 @@ class ReactNativeBleTransport {
1037
1142
  const transport = this.getCachedTransport(uuid);
1038
1143
  const runPromise = hdShared.createDeferred();
1039
1144
  runPromise.promise.catch(() => undefined);
1145
+ const supersededRunPromise = this.runPromise;
1146
+ if (supersededRunPromise) {
1147
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1148
+ }
1040
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;
1041
1158
  const messages = this._messages;
1042
1159
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1043
1160
  let timeout;
@@ -1058,6 +1175,9 @@ class ReactNativeBleTransport {
1058
1175
  }
1059
1176
  catch (e) {
1060
1177
  onError(e);
1178
+ if (isWedgedWriteError(e)) {
1179
+ throw e;
1180
+ }
1061
1181
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1062
1182
  }
1063
1183
  }
@@ -1085,6 +1205,9 @@ class ReactNativeBleTransport {
1085
1205
  }
1086
1206
  catch (e) {
1087
1207
  onError(e);
1208
+ if (isWedgedWriteError(e)) {
1209
+ throw e;
1210
+ }
1088
1211
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1089
1212
  }
1090
1213
  }
@@ -1095,13 +1218,13 @@ class ReactNativeBleTransport {
1095
1218
  });
1096
1219
  }
1097
1220
  if (name === 'EmmcFileWrite') {
1098
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1099
- this.runPromise = null;
1221
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1222
+ releaseOwnershipIfCurrent();
1100
1223
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1101
1224
  });
1102
1225
  }
1103
1226
  else if (name === 'FirmwareUpload') {
1104
- 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', {
1105
1228
  packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
1106
1229
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1107
1230
  pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
@@ -1112,7 +1235,7 @@ class ReactNativeBleTransport {
1112
1235
  let attempt = 0;
1113
1236
  while (true) {
1114
1237
  try {
1115
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1238
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1116
1239
  return;
1117
1240
  }
1118
1241
  catch (error) {
@@ -1120,36 +1243,18 @@ class ReactNativeBleTransport {
1120
1243
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1121
1244
  throw error;
1122
1245
  }
1123
- const shouldReconnect = retryType === 'reconnectable';
1124
- const delayMs = shouldReconnect
1125
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1126
- : resolveFirmwareUploadRetryDelay(attempt);
1246
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1127
1247
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1128
1248
  attempt: attempt + 1,
1129
1249
  delayMs,
1130
- reconnect: shouldReconnect,
1131
1250
  error,
1132
1251
  });
1133
- if (shouldReconnect) {
1134
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1135
- }
1136
1252
  yield delay(delayMs);
1137
1253
  attempt += 1;
1138
- if (shouldReconnect) {
1139
- try {
1140
- yield this.reconnectFirmwareUploadTransport(uuid, transport);
1141
- }
1142
- catch (e) {
1143
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
1144
- if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1145
- throw e;
1146
- }
1147
- }
1148
- }
1149
1254
  }
1150
1255
  }
1151
1256
  }), e => {
1152
- this.runPromise = null;
1257
+ releaseOwnershipIfCurrent();
1153
1258
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1154
1259
  });
1155
1260
  }
@@ -1157,11 +1262,17 @@ class ReactNativeBleTransport {
1157
1262
  for (const o of buffers) {
1158
1263
  const outData = o.toString('base64');
1159
1264
  try {
1160
- 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);
1161
1269
  }
1162
1270
  catch (e) {
1163
1271
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1164
- this.runPromise = null;
1272
+ releaseOwnershipIfCurrent();
1273
+ if (isWedgedWriteError(e)) {
1274
+ throw e;
1275
+ }
1165
1276
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1166
1277
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1167
1278
  }
@@ -1190,17 +1301,23 @@ class ReactNativeBleTransport {
1190
1301
  if (typeof response !== 'string') {
1191
1302
  throw new Error('Returning data is not string.');
1192
1303
  }
1193
- Log === null || Log === void 0 ? void 0 : Log.debug('receive data: ', response);
1194
1304
  const jsonData = ProtocolV1.decodeMessage(messages, response);
1195
1305
  return check.call(jsonData);
1196
1306
  }
1197
1307
  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);
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);
1200
1310
  }
1201
1311
  else {
1202
1312
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1203
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
+ }
1204
1321
  throw e;
1205
1322
  }
1206
1323
  finally {
@@ -1208,6 +1325,7 @@ class ReactNativeBleTransport {
1208
1325
  clearTimeout(timeout);
1209
1326
  if (this.runPromise === runPromise) {
1210
1327
  this.runPromise = null;
1328
+ this.runPromiseDeviceId = null;
1211
1329
  }
1212
1330
  }
1213
1331
  });
@@ -1216,10 +1334,11 @@ class ReactNativeBleTransport {
1216
1334
  this.stopped = true;
1217
1335
  }
1218
1336
  disconnect(session) {
1219
- var _a, _b, _c, _d, _e, _f;
1337
+ var _a, _b, _c, _d, _e;
1220
1338
  return __awaiter(this, void 0, void 0, function* () {
1221
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport resetSession: ', session);
1339
+ yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1222
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);
1223
1342
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1224
1343
  try {
1225
1344
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1232,7 +1351,7 @@ class ReactNativeBleTransport {
1232
1351
  }
1233
1352
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1234
1353
  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);
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);
1236
1355
  transport.notifySubscription.remove();
1237
1356
  transport.notifySubscription = undefined;
1238
1357
  }
@@ -1242,7 +1361,7 @@ class ReactNativeBleTransport {
1242
1361
  }
1243
1362
  if (session) {
1244
1363
  try {
1245
- 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));
1246
1365
  }
1247
1366
  catch (e) {
1248
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);
@@ -1257,7 +1376,7 @@ class ReactNativeBleTransport {
1257
1376
  }
1258
1377
  }
1259
1378
  try {
1260
- 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));
1261
1380
  }
1262
1381
  catch (e) {
1263
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);
@@ -1266,22 +1385,21 @@ class ReactNativeBleTransport {
1266
1385
  delete transportCache[session];
1267
1386
  }
1268
1387
  this.deviceProtocol.delete(session);
1388
+ this.probingProtocols.delete(session);
1269
1389
  this.deviceProtocolHints.delete(session);
1390
+ this.sessionProtocols.delete(session);
1391
+ this.protocolReprobeFailures.delete(session);
1270
1392
  this.protocolV2Assemblers.delete(session);
1271
1393
  this.resetProtocolV2Frames(session);
1272
- if (((_d = this.activeProtocolV2Call) === null || _d === void 0 ? void 0 : _d.uuid) === session) {
1273
- this.activeProtocolV2Call = null;
1274
- }
1275
1394
  try {
1276
- (_e = this.emitter) === null || _e === void 0 ? void 0 : _e.emit('device-disconnect', {
1277
- name: (_f = transport === null || transport === void 0 ? void 0 : transport.device) === null || _f === void 0 ? void 0 : _f.name,
1278
- id: session,
1279
- connectId: session,
1280
- });
1395
+ this.emitDeviceDisconnect(session, (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name, monitorToken);
1281
1396
  }
1282
1397
  catch (e) {
1283
1398
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1284
1399
  }
1400
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1401
+ this.monitorTokens.delete(session);
1402
+ }
1285
1403
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1286
1404
  });
1287
1405
  }
@@ -1289,6 +1407,92 @@ class ReactNativeBleTransport {
1289
1407
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1290
1408
  if (this.runPromise) ;
1291
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
+ }
1292
1496
  }
1293
1497
  getCachedTransport(uuid) {
1294
1498
  const transport = transportCache[uuid];
@@ -1297,58 +1501,189 @@ class ReactNativeBleTransport {
1297
1501
  }
1298
1502
  return transport;
1299
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
+ }
1300
1582
  createProtocolMismatchError(expected) {
1301
1583
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1302
1584
  }
1303
1585
  createProtocolDetectionError() {
1304
- 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');
1305
1587
  }
1306
1588
  clearProbeProtocol(uuid, protocol) {
1589
+ if (this.probingProtocols.get(uuid) === protocol) {
1590
+ this.probingProtocols.delete(uuid);
1591
+ }
1307
1592
  if (this.deviceProtocol.get(uuid) === protocol) {
1308
1593
  this.deviceProtocol.delete(uuid);
1309
1594
  }
1310
1595
  }
1311
- 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;
1312
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
+ }
1313
1612
  if (expectedProtocol === 'V1') {
1314
1613
  if (yield this.probeProtocolV1(uuid)) {
1315
1614
  this.deviceProtocol.set(uuid, 'V1');
1316
- 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
+ });
1317
1621
  return 'V1';
1318
1622
  }
1319
1623
  throw this.createProtocolMismatchError(expectedProtocol);
1320
1624
  }
1321
1625
  if (expectedProtocol === 'V2') {
1322
- this.deviceProtocol.set(uuid, 'V2');
1323
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1324
- 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);
1325
1637
  }
1326
- 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;
1327
1645
  for (let i = 0; i < probeOrder.length; i += 1) {
1328
1646
  const protocol = probeOrder[i];
1329
1647
  if (i > 0) {
1330
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
+ }
1331
1655
  }
1332
1656
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1333
1657
  if (detected) {
1334
1658
  this.deviceProtocol.set(uuid, protocol);
1335
- 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
+ });
1336
1666
  return protocol;
1337
1667
  }
1338
1668
  }
1669
+ if (trustSessionProtocol) {
1670
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1671
+ }
1672
+ else {
1673
+ this.protocolReprobeFailures.delete(uuid);
1674
+ }
1339
1675
  this.deviceProtocol.delete(uuid);
1676
+ this.probingProtocols.delete(uuid);
1340
1677
  throw this.createProtocolDetectionError();
1341
1678
  });
1342
1679
  }
1343
1680
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1344
- var _a, _b, _c, _d;
1681
+ var _a, _b, _c;
1345
1682
  return __awaiter(this, void 0, void 0, function* () {
1346
1683
  const transport = transportCache[uuid];
1684
+ yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1347
1685
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1348
1686
  this.resetProtocolV2Frames(uuid);
1349
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
1350
- this.activeProtocolV2Call = null;
1351
- }
1352
1687
  if (this.runPromise) {
1353
1688
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1354
1689
  this.runPromise.reject(error);
@@ -1360,11 +1695,11 @@ class ReactNativeBleTransport {
1360
1695
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
1361
1696
  this.monitorTokens.delete(uuid);
1362
1697
  }
1363
- (_c = transport.notifySubscription) === null || _c === void 0 ? void 0 : _c.remove();
1698
+ (_b = transport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
1364
1699
  transport.notifySubscription = undefined;
1365
1700
  if (previousNotifyTransactionId) {
1366
1701
  try {
1367
- yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelTransaction(previousNotifyTransactionId));
1702
+ yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(previousNotifyTransactionId));
1368
1703
  }
1369
1704
  catch (error) {
1370
1705
  Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`, (error === null || error === void 0 ? void 0 : error.message) || error);
@@ -1390,13 +1725,17 @@ class ReactNativeBleTransport {
1390
1725
  return false;
1391
1726
  }
1392
1727
  try {
1393
- this.deviceProtocol.set(uuid, 'V1');
1394
- 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);
1395
1731
  return true;
1396
1732
  }
1397
1733
  catch (error) {
1398
1734
  this.clearProbeProtocol(uuid, 'V1');
1399
- 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
+ }
1400
1739
  return false;
1401
1740
  }
1402
1741
  });
@@ -1407,7 +1746,7 @@ class ReactNativeBleTransport {
1407
1746
  if (!this._messages || !this._messagesV2) {
1408
1747
  return false;
1409
1748
  }
1410
- this.deviceProtocol.set(uuid, 'V2');
1749
+ this.probingProtocols.set(uuid, 'V2');
1411
1750
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1412
1751
  const detected = yield transport.probeProtocolV2({
1413
1752
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1423,17 +1762,16 @@ class ReactNativeBleTransport {
1423
1762
  if (!detected) {
1424
1763
  this.clearProbeProtocol(uuid, 'V2');
1425
1764
  }
1765
+ else {
1766
+ this.probingProtocols.delete(uuid);
1767
+ }
1426
1768
  return detected;
1427
1769
  });
1428
1770
  }
1429
- handleProtocolV2Notification(uuid, data) {
1430
- var _a, _b, _c;
1771
+ handleProtocolV2Notification(uuid, monitorToken, data) {
1431
1772
  try {
1432
- if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== uuid) {
1433
- (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
1434
- this.resetProtocolV2Frames(uuid);
1773
+ if (this.monitorTokens.get(uuid) !== monitorToken)
1435
1774
  return;
1436
- }
1437
1775
  if (data.length === 0)
1438
1776
  return;
1439
1777
  const assembler = this.protocolV2Assemblers.get(uuid);
@@ -1446,8 +1784,10 @@ class ReactNativeBleTransport {
1446
1784
  catch (error) {
1447
1785
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
1448
1786
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1449
- (_c = this.runPromise) === null || _c === void 0 ? void 0 : _c.reject(notifyError);
1450
- this.rejectAllProtocolV2Frames(notifyError);
1787
+ this.rejectProtocolV2Frames(uuid, notifyError);
1788
+ this.protocolV2Links
1789
+ .invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
1790
+ .catch(invalidateError => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 notify cleanup failed:', invalidateError));
1451
1791
  }
1452
1792
  }
1453
1793
  getProtocolV2FrameQueue(uuid) {
@@ -1467,20 +1807,16 @@ class ReactNativeBleTransport {
1467
1807
  }
1468
1808
  this.getProtocolV2FrameQueue(uuid).push(frame);
1469
1809
  }
1470
- rejectAllProtocolV2Frames(error) {
1471
- this.protocolV2FrameQueues.clear();
1472
- for (const framePromise of this.protocolV2FramePromises.values()) {
1473
- framePromise.reject(error);
1474
- }
1475
- this.protocolV2FramePromises.clear();
1476
- }
1477
1810
  resetProtocolV2Frames(uuid) {
1478
- this.protocolV2FrameQueues.delete(uuid);
1479
- this.protocolV2FramePromises.delete(uuid);
1811
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1480
1812
  }
1481
- isActiveProtocolV2Call(uuid, token) {
1482
- var _a;
1483
- return ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === uuid && this.activeProtocolV2Call.token === token;
1813
+ rejectProtocolV2Frames(uuid, error) {
1814
+ this.protocolV2FrameQueues.delete(uuid);
1815
+ const framePromise = this.protocolV2FramePromises.get(uuid);
1816
+ if (framePromise) {
1817
+ this.protocolV2FramePromises.delete(uuid);
1818
+ framePromise.reject(error);
1819
+ }
1484
1820
  }
1485
1821
  readProtocolV2Frame(uuid) {
1486
1822
  return __awaiter(this, void 0, void 0, function* () {
@@ -1500,146 +1836,255 @@ class ReactNativeBleTransport {
1500
1836
  }
1501
1837
  });
1502
1838
  }
1503
- writeProtocolV2Frame(transport, frame, options) {
1839
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1504
1840
  return __awaiter(this, void 0, void 0, function* () {
1505
- const tuning = getProtocolV2BleTuning();
1506
- const packetCapacity = resolveProtocolV2PacketCapacity({
1841
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
1507
1842
  platform: reactNative.Platform.OS,
1508
- iosPacketLength: tuning.iosPacketLength,
1509
- androidPacketLength: tuning.androidPacketLength,
1510
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
1843
+ highThroughput: context.highThroughput,
1844
+ requestedWithResponse: context.writeWithResponse,
1845
+ characteristic: transport.writeCharacteristic,
1511
1846
  });
1512
- const writeWithResponse = !!(options === null || options === void 0 ? void 0 : options.writeWithResponse) || (!!(options === null || options === void 0 ? void 0 : options.highVolume) && tuning.highVolumeWriteWithResponse);
1513
- const writeMode = resolveBleWriteMode(transport.writeCharacteristic, writeWithResponse ? 'withResponse' : 'withoutResponse');
1514
- const shouldThrottle = !!(options === null || options === void 0 ? void 0 : options.highVolume) && writeMode === 'withoutResponse';
1515
- let packetsWritten = 0;
1516
- try {
1517
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1518
- const chunk = frame.slice(offset, offset + packetCapacity);
1519
- const base64 = buffer.Buffer.from(chunk).toString('base64');
1520
- if (writeMode === 'withResponse') {
1521
- yield transport.writeCharacteristic.writeWithResponse(base64);
1522
- }
1523
- else {
1524
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1525
- }
1526
- packetsWritten += 1;
1527
- if (shouldThrottle &&
1528
- packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
1529
- offset + packetCapacity < frame.length) {
1530
- yield delay(tuning.highVolumeWritePauseMs);
1531
- }
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}`);
1532
1852
  }
1533
- if (shouldThrottle) {
1534
- yield delay(tuning.highVolumeWriteFlushDelayMs);
1535
- }
1536
- }
1537
- catch (error) {
1538
- if ((options === null || options === void 0 ? void 0 : options.highVolume) && !writeWithResponse && packetsWritten === 0) {
1539
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:', error);
1540
- yield this.writeProtocolV2Frame(transport, frame, {
1541
- highVolume: true,
1542
- writeWithResponse: true,
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
+ }
1543
1864
  });
1865
+ assertCurrentGeneration();
1544
1866
  return;
1545
1867
  }
1546
- throw error;
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);
1881
+ }
1547
1882
  }
1548
1883
  });
1549
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
+ });
1903
+ });
1904
+ }
1550
1905
  callProtocolV2(uuid, name, data, options) {
1551
- var _a, _b, _c, _d;
1906
+ var _a;
1552
1907
  return __awaiter(this, void 0, void 0, function* () {
1553
1908
  if (!this._messages || !this._messagesV2) {
1554
1909
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1555
1910
  }
1556
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
1557
- if (this.runPromise) {
1558
- if (!forceRun) {
1559
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1560
- }
1561
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1562
- this.runPromise.reject(error);
1563
- this.rejectAllProtocolV2Frames(error);
1564
- this.runPromise = null;
1565
- this.activeProtocolV2Call = null;
1566
- }
1567
- const transport$1 = this.getCachedTransport(uuid);
1568
- const runPromise = hdShared.createDeferred();
1569
- runPromise.promise.catch(() => undefined);
1570
- this.runPromise = runPromise;
1571
- const callToken = this.nextProtocolV2CallToken++;
1572
- this.activeProtocolV2Call = { uuid, token: callToken };
1573
- (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1574
- this.resetProtocolV2Frames(uuid);
1575
- let completed = false;
1576
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : BLE_RESPONSE_TIMEOUT_MS });
1577
- const highVolumeWrite = transport.LogBlockCommand.has(name);
1578
- if (highVolumeWrite) {
1911
+ const callOptions = options;
1912
+ const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
1913
+ if (highThroughputWrite) {
1914
+ yield this.ensureProtocolV2HighThroughputMtu(uuid);
1579
1915
  const tuning = getProtocolV2BleTuning();
1580
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:', name, {
1581
- packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1582
- burstSize: tuning.highVolumeWriteBurstSize,
1583
- pauseMs: tuning.highVolumeWritePauseMs,
1584
- flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
1585
- 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,
1922
+ });
1923
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1924
+ platform: reactNative.Platform.OS,
1925
+ iosPacketLength: tuning.iosPacketLength,
1926
+ androidPacketLength: tuning.androidPacketLength,
1927
+ mtu: currentTransport.mtuSize,
1586
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);
1587
1945
  }
1588
1946
  try {
1589
- const session = new transport.ProtocolV2Session({
1590
- schemas: {
1591
- protocolV1: this._messages,
1592
- protocolV2: this._messagesV2,
1593
- },
1594
- router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1595
- writeFrame: (frame) => __awaiter(this, void 0, void 0, function* () {
1596
- yield this.writeProtocolV2Frame(transport$1, frame, {
1597
- highVolume: highVolumeWrite,
1598
- });
1599
- }),
1600
- readFrame: () => __awaiter(this, void 0, void 0, function* () {
1601
- const rxFrame = yield this.readProtocolV2Frame(uuid);
1602
- if (!(rxFrame instanceof Uint8Array)) {
1603
- throw new Error('Protocol V2 response is not Uint8Array');
1604
- }
1605
- return rxFrame;
1606
- }),
1607
- logger: Log,
1608
- logPrefix: 'ProtocolV2 RN-BLE',
1609
- createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
1610
- });
1611
- const result = yield session.call(name, data, callOptions);
1612
- completed = true;
1613
- return result;
1947
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1614
1948
  }
1615
1949
  catch (e) {
1616
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1617
- (_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
1618
- this.resetProtocolV2Frames(uuid);
1619
- }
1620
1950
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1621
1951
  throw e;
1622
1952
  }
1623
1953
  finally {
1624
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1625
- if (!completed) {
1626
- (_d = this.protocolV2Assemblers.get(uuid)) === null || _d === void 0 ? void 0 : _d.reset();
1627
- }
1628
- this.resetProtocolV2Frames(uuid);
1629
- this.activeProtocolV2Call = null;
1630
- }
1631
- if (this.runPromise === runPromise) {
1632
- this.runPromise = null;
1954
+ if (highThroughputWrite) {
1955
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
1633
1956
  }
1634
1957
  }
1635
1958
  });
1636
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
+ }
2032
+ });
2033
+ }
2034
+ createProtocolV2Adapter(uuid) {
2035
+ var _a;
2036
+ const generation = (_a = this.monitorTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
2037
+ const assertCurrentGeneration = () => {
2038
+ if (this.monitorTokens.get(uuid) !== generation) {
2039
+ throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
2040
+ }
2041
+ };
2042
+ return {
2043
+ router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
2044
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
2045
+ generation,
2046
+ prepareCall: () => {
2047
+ var _a;
2048
+ assertCurrentGeneration();
2049
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2050
+ this.resetProtocolV2Frames(uuid);
2051
+ },
2052
+ writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
2053
+ assertCurrentGeneration();
2054
+ const currentTransport = this.getCachedTransport(uuid);
2055
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
2056
+ }),
2057
+ readFrame: () => __awaiter(this, void 0, void 0, function* () {
2058
+ assertCurrentGeneration();
2059
+ const rxFrame = yield this.readProtocolV2Frame(uuid);
2060
+ if (!(rxFrame instanceof Uint8Array)) {
2061
+ throw new Error('Protocol V2 response is not Uint8Array');
2062
+ }
2063
+ return rxFrame;
2064
+ }),
2065
+ reset: (reason) => {
2066
+ var _a;
2067
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2068
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
2069
+ },
2070
+ logger: Log,
2071
+ logPrefix: 'ProtocolV2 RN-BLE',
2072
+ createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
2073
+ };
2074
+ }
1637
2075
  getProtocolType(path) {
1638
- return this.deviceProtocol.get(path);
2076
+ return this.getActiveProtocol(path);
1639
2077
  }
1640
2078
  }
1641
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;
1642
2086
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1643
2087
  exports["default"] = ReactNativeBleTransport;
2088
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1644
2089
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1645
2090
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;