@onekeyfe/hd-transport-react-native 1.2.0-alpha.7 → 1.2.0-alpha.71

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 = 514;
97
98
  const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
98
99
  const OneKeyServices = {
99
100
  classic: {
@@ -117,51 +118,37 @@ 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';
138
+ function resolveProtocolV2PacketCapacity({ platform, iosPacketLength = IOS_PROTOCOL_V2_PACKET_LENGTH, androidPacketLength = ANDROID_PROTOCOL_V2_PACKET_LENGTH, mtu, }) {
139
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 3) {
140
+ throw new Error(`Protocol V2 BLE requires a negotiated MTU, received: ${String(mtu)}`);
147
141
  }
148
- if (characteristic.isWritableWithoutResponse) {
149
- return 'withoutResponse';
150
- }
151
- if (characteristic.isWritableWithResponse) {
152
- return 'withResponse';
153
- }
154
- return preferredMode;
142
+ const payloadLength = Math.floor(mtu) - 3;
143
+ const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
144
+ return Math.min(configuredPacketLength, payloadLength);
155
145
  }
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;
146
+ function shouldWriteProtocolV2WithResponse({ platform, highVolume, requestedWithResponse, characteristic, }) {
147
+ if (!characteristic.isWritableWithResponse)
148
+ return false;
149
+ if (!characteristic.isWritableWithoutResponse)
150
+ return true;
151
+ return requestedWithResponse === true || (platform === 'ios' && !highVolume);
165
152
  }
166
153
 
167
154
  const timer = process.env.NODE_ENV === 'development'
@@ -189,7 +176,6 @@ const timer = process.env.NODE_ENV === 'development'
189
176
  const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject) => {
190
177
  let done = false;
191
178
  const subscription = bleManager.onStateChange(state => {
192
- console.log('ble state -> ', state);
193
179
  if (state === 'PoweredOn') {
194
180
  if (done)
195
181
  return;
@@ -219,47 +205,24 @@ const isHeaderChunk = (chunk) => {
219
205
  return false;
220
206
  };
221
207
 
222
- const Log$1 = bleLogger;
223
208
  class BleTransport {
224
209
  constructor(device, writeCharacteristic, notifyCharacteristic) {
225
210
  this.name = 'ReactNativeBleTransport';
226
- this.mtuSize = 23;
227
211
  this.id = device.id;
228
212
  this.device = device;
229
213
  this.writeCharacteristic = writeCharacteristic;
230
214
  this.notifyCharacteristic = notifyCharacteristic;
231
215
  }
232
- writeWithRetry(data, retryCount = BleTransport.MAX_RETRIES) {
216
+ writeWithRetry(data) {
233
217
  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;
218
+ if (reactNative.Platform.OS === 'ios' && this.writeCharacteristic.isWritableWithResponse) {
219
+ yield this.writeCharacteristic.writeWithResponse(data);
220
+ return;
257
221
  }
222
+ yield this.writeCharacteristic.writeWithoutResponse(data);
258
223
  });
259
224
  }
260
225
  }
261
- BleTransport.MAX_RETRIES = 5;
262
- BleTransport.RETRY_DELAY = 2000;
263
226
 
264
227
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
265
228
  const Log = bleLogger;
@@ -268,10 +231,35 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
268
231
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
269
232
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
270
233
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
271
- const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
272
234
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
273
235
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
274
236
  const ANDROID_GATT_CONGESTED_STATUS = 143;
237
+ const isAsciiWhitespace = (code) => code === 0x09 ||
238
+ code === 0x0a ||
239
+ code === 0x0b ||
240
+ code === 0x0c ||
241
+ code === 0x0d ||
242
+ code === 0x20;
243
+ const hasGattCongestedStatus = (text) => {
244
+ let searchFrom = 0;
245
+ while (searchFrom < text.length) {
246
+ const statusIndex = text.indexOf('status', searchFrom);
247
+ if (statusIndex < 0)
248
+ return false;
249
+ let cursor = statusIndex + 'status'.length;
250
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
251
+ cursor += 1;
252
+ if (text[cursor] === ':' || text[cursor] === '=') {
253
+ cursor += 1;
254
+ while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
255
+ cursor += 1;
256
+ }
257
+ if (text.startsWith(String(ANDROID_GATT_CONGESTED_STATUS), cursor))
258
+ return true;
259
+ searchFrom = statusIndex + 'status'.length;
260
+ }
261
+ return false;
262
+ };
275
263
  const delay = (ms) => new Promise(resolve => {
276
264
  setTimeout(resolve, ms);
277
265
  });
@@ -279,10 +267,6 @@ const getFirmwareUploadWriteRetryType = (error) => {
279
267
  if (!error || typeof error !== 'object')
280
268
  return null;
281
269
  const bleWriteError = error;
282
- if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
283
- bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
284
- return 'reconnectable';
285
- }
286
270
  if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
287
271
  bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
288
272
  return 'congested';
@@ -290,25 +274,23 @@ const getFirmwareUploadWriteRetryType = (error) => {
290
274
  const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
291
275
  .filter(value => typeof value === 'string')
292
276
  .join(' ');
293
- return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
277
+ return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
294
278
  };
295
279
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
296
- const BLE_RESPONSE_TIMEOUT_MS = 30000;
297
280
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
298
281
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
299
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
282
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
283
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
284
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
285
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
286
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
287
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
288
+ const DEVICE_SCAN_TIMEOUT_MS = 3000;
300
289
  const IOS_NOTIFY_READY_DELAY_MS = 150;
301
290
  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
291
  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,
292
+ iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
293
+ androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
312
294
  };
313
295
  let protocolV2BleTuning = Object.assign({}, DEFAULT_PROTOCOL_V2_BLE_TUNING);
314
296
  const normalizePositiveInteger = (value, fallback) => {
@@ -318,20 +300,15 @@ const normalizePositiveInteger = (value, fallback) => {
318
300
  return Math.floor(normalized);
319
301
  };
320
302
  function configureProtocolV2BleTuning(tuning = {}) {
321
- var _a;
322
303
  protocolV2BleTuning = {
323
304
  iosPacketLength: normalizePositiveInteger(tuning.iosPacketLength, protocolV2BleTuning.iosPacketLength),
324
305
  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
306
  };
330
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
307
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
331
308
  }
332
309
  function resetProtocolV2BleTuning() {
333
310
  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);
311
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
335
312
  }
336
313
  function getProtocolV2BleTuning() {
337
314
  return Object.assign({}, protocolV2BleTuning);
@@ -342,19 +319,30 @@ function inferProtocolHintFromDeviceName(name) {
342
319
  function getDeviceDisplayName(device) {
343
320
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
344
321
  }
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;
322
+ const IOS_REQUEST_MTU = 247;
323
+ const ANDROID_REQUEST_MTU = 517;
324
+ const BLE_MTU_REFRESH_THRESHOLD = 247;
325
+ const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
326
+ const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
327
+ const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
328
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
353
329
  const connectOptions = {
354
- requestMTU: ANDROID_REQUEST_MTU,
355
- timeout: 3000,
330
+ requestMTU: getRequestedBleMtu(),
331
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
356
332
  refreshGatt: 'OnConnected',
357
333
  };
334
+ const fallbackConnectOptions = {
335
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
336
+ };
337
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
338
+ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
339
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
340
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
341
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
342
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
343
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
344
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
345
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
358
346
  const tryToGetConfiguration = (device) => {
359
347
  if (!device || !device.serviceUUIDs)
360
348
  return null;
@@ -366,22 +354,25 @@ const tryToGetConfiguration = (device) => {
366
354
  return null;
367
355
  return infos;
368
356
  };
369
- const requestAndroidMtu = (device) => __awaiter(void 0, void 0, void 0, function* () {
370
- if (reactNative.Platform.OS !== 'android')
357
+ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
358
+ if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
371
359
  return device;
372
360
  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
- });
361
+ const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
378
362
  return mtuDevice;
379
363
  }
380
364
  catch (error) {
381
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
365
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
366
+ platform: reactNative.Platform.OS,
367
+ stage,
368
+ attempt,
369
+ actual: device.mtu,
370
+ error: error instanceof Error ? error.message : String(error),
371
+ });
382
372
  return device;
383
373
  }
384
374
  });
375
+ const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
385
376
  function remapError(error) {
386
377
  var _a;
387
378
  if (error instanceof reactNativeBlePlx.BleError) {
@@ -408,15 +399,44 @@ class ReactNativeBleTransport {
408
399
  this.stopped = false;
409
400
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
410
401
  this.runPromise = null;
402
+ this.runPromiseDeviceId = null;
411
403
  this.firmwareUploadWriteRecoveryIds = new Set();
412
404
  this.deviceProtocol = new Map();
405
+ this.probingProtocols = new Map();
406
+ this.writeTimeoutCounts = new Map();
407
+ this.connectionSetupTimeoutCounts = new Map();
413
408
  this.deviceProtocolHints = new Map();
409
+ this.sessionProtocols = new Map();
410
+ this.protocolReprobeFailures = new Map();
414
411
  this.protocolV2Assemblers = new Map();
415
412
  this.protocolV2FrameQueues = new Map();
416
413
  this.protocolV2FramePromises = new Map();
417
- this.activeProtocolV2Call = null;
418
- this.nextProtocolV2CallToken = 1;
414
+ this.protocolV2Links = new transport.ProtocolV2LinkManager({
415
+ getSchemas: () => {
416
+ if (!this._messages || !this._messagesV2) {
417
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
418
+ }
419
+ return {
420
+ protocolV1: this._messages,
421
+ protocolV2: this._messagesV2,
422
+ };
423
+ },
424
+ classifyError: () => 'link-fatal',
425
+ onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
426
+ var _b;
427
+ (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
428
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
429
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
430
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
431
+ yield this.releaseNative(uuid, true);
432
+ }
433
+ }),
434
+ });
419
435
  this.monitorTokens = new Map();
436
+ this.disconnectEventTokens = new Map();
437
+ this.protocolV2HighVolumeLogSignatures = new Map();
438
+ this.androidHighPriorityDevices = new Set();
439
+ this.androidPriorityResetTimers = new Map();
420
440
  this.nextMonitorToken = 1;
421
441
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
422
442
  }
@@ -430,8 +450,18 @@ class ReactNativeBleTransport {
430
450
  this._messages = messages;
431
451
  }
432
452
  configureProtocolV2(signedData) {
453
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
454
+ if (this.protocolV2SchemaConfiguration === configuration) {
455
+ return;
456
+ }
457
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
433
458
  this._messagesV2 = parseConfigure(signedData);
434
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
459
+ this.protocolV2SchemaConfiguration = configuration;
460
+ if (isReconfiguration) {
461
+ this.protocolV2Links
462
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
463
+ .catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('Protocol V2 schema link cleanup failed:', error));
464
+ }
435
465
  }
436
466
  listen() {
437
467
  }
@@ -442,7 +472,6 @@ class ReactNativeBleTransport {
442
472
  return Promise.resolve(this.blePlxManager);
443
473
  }
444
474
  resolveCharacteristics(device) {
445
- var _a, _b, _c, _d;
446
475
  return __awaiter(this, void 0, void 0, function* () {
447
476
  yield device.discoverAllServicesAndCharacteristics();
448
477
  let infos = tryToGetConfiguration(device);
@@ -459,19 +488,11 @@ class ReactNativeBleTransport {
459
488
  }
460
489
  }
461
490
  }
462
- let fallbackServiceUuid;
463
491
  if (!infos) {
464
492
  const services = yield device.services();
465
493
  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
494
  }
474
- if (!infos && !fallbackServiceUuid) {
495
+ if (!infos) {
475
496
  try {
476
497
  Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
477
498
  yield device.cancelConnection();
@@ -481,9 +502,7 @@ class ReactNativeBleTransport {
481
502
  }
482
503
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
483
504
  }
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';
505
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
487
506
  if (!serviceUuid) {
488
507
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
489
508
  }
@@ -524,8 +543,8 @@ class ReactNativeBleTransport {
524
543
  attachDisconnectSubscription(transport, device, uuid) {
525
544
  var _a;
526
545
  (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
546
+ const { monitorToken } = transport;
527
547
  transport.disconnectSubscription = device.onDisconnected(() => {
528
- var _a;
529
548
  if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
530
549
  Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
531
550
  return;
@@ -534,17 +553,16 @@ class ReactNativeBleTransport {
534
553
  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
554
  return;
536
555
  }
556
+ if (this.monitorTokens.get(uuid) !== monitorToken) {
557
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored for stale generation: ', device === null || device === void 0 ? void 0 : device.id);
558
+ return;
559
+ }
537
560
  try {
538
561
  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) {
562
+ this.emitDeviceDisconnect(uuid, device === null || device === void 0 ? void 0 : device.name, monitorToken);
563
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
545
564
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError);
546
565
  this.runPromise.reject(error);
547
- this.rejectAllProtocolV2Frames(error);
548
566
  }
549
567
  }
550
568
  catch (e) {
@@ -555,6 +573,22 @@ class ReactNativeBleTransport {
555
573
  }
556
574
  });
557
575
  }
576
+ emitDeviceDisconnect(uuid, name, token) {
577
+ var _a;
578
+ if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
579
+ return;
580
+ }
581
+ if (this.monitorTokens.get(uuid) !== token) {
582
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect event ignored for stale generation: ', uuid);
583
+ return;
584
+ }
585
+ this.disconnectEventTokens.set(uuid, token);
586
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
587
+ name,
588
+ id: uuid,
589
+ connectId: uuid,
590
+ });
591
+ }
558
592
  reconnectFirmwareUploadTransport(uuid, transport) {
559
593
  var _a, _b;
560
594
  return __awaiter(this, void 0, void 0, function* () {
@@ -568,19 +602,19 @@ class ReactNativeBleTransport {
568
602
  const isConnected = yield device.isConnected().catch(() => false);
569
603
  if (!isConnected) {
570
604
  try {
571
- device = yield device.connect(connectOptions);
605
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
572
606
  }
573
607
  catch (e) {
574
608
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
575
609
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
576
- device = yield device.connect();
610
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
577
611
  }
578
612
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
579
613
  throw e;
580
614
  }
581
615
  }
582
616
  }
583
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
617
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
584
618
  transport.device = device;
585
619
  transport.writeCharacteristic = writeCharacteristic;
586
620
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -624,13 +658,12 @@ class ReactNativeBleTransport {
624
658
  return;
625
659
  }
626
660
  }
627
- blePlxManager.startDeviceScan(null, {
661
+ blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
628
662
  allowDuplicates: true,
629
663
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
630
664
  }, (error, device) => {
631
- var _a, _b, _c;
665
+ var _a;
632
666
  if (error) {
633
- Log === null || Log === void 0 ? void 0 : Log.debug('ble scan manager: ', blePlxManager);
634
667
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
635
668
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
636
669
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
@@ -650,25 +683,19 @@ class ReactNativeBleTransport {
650
683
  return;
651
684
  }
652
685
  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', {
686
+ const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
687
+ const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.name) ||
688
+ hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.localName);
689
+ const isOneKey = !isUnnamedIOSPeripheral &&
690
+ !isFindMyPeripheral &&
691
+ hdShared.isOnekeyBluetoothDevice({
692
+ id: device === null || device === void 0 ? void 0 : device.id,
659
693
  name: device === null || device === void 0 ? void 0 : device.name,
660
694
  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,
695
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
664
696
  });
665
- }
666
697
  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
698
  addDevice(device);
671
- Log === null || Log === void 0 ? void 0 : Log.debug('search device end ======================\n');
672
699
  }
673
700
  else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
674
701
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
@@ -679,10 +706,23 @@ class ReactNativeBleTransport {
679
706
  });
680
707
  }
681
708
  });
682
- getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
709
+ getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
683
710
  for (const device of devices) {
684
- Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
685
- addDevice(device);
711
+ const localName = 'localName' in device && typeof device.localName === 'string'
712
+ ? device.localName
713
+ : null;
714
+ const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device.name) ||
715
+ hdShared.isPro2FindMyAdvertisementName(localName);
716
+ if (!isFindMyPeripheral &&
717
+ hdShared.isOnekeyBluetoothDevice({
718
+ id: device.id,
719
+ name: device.name,
720
+ localName,
721
+ serviceUuids: device.serviceUUIDs,
722
+ })) {
723
+ Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
724
+ addDevice(device);
725
+ }
686
726
  }
687
727
  });
688
728
  const addDevice = (device) => {
@@ -694,6 +734,12 @@ class ReactNativeBleTransport {
694
734
  this.deviceProtocolHints.set(device.id, protocolHint);
695
735
  }
696
736
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
737
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
738
+ deviceId: device.id,
739
+ name: displayName,
740
+ serviceUUIDs: device.serviceUUIDs,
741
+ protocolHint,
742
+ });
697
743
  }
698
744
  };
699
745
  timer.timeout(() => {
@@ -703,6 +749,57 @@ class ReactNativeBleTransport {
703
749
  }));
704
750
  });
705
751
  }
752
+ installTransportForAcquire(uuid, device, characteristics) {
753
+ return __awaiter(this, void 0, void 0, function* () {
754
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
755
+ const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
756
+ transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
757
+ const monitorToken = this.nextMonitorToken;
758
+ this.nextMonitorToken += 1;
759
+ const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
760
+ transport$1.monitorToken = monitorToken;
761
+ transport$1.notifyTransactionId = notifyTransactionId;
762
+ this.monitorTokens.set(uuid, monitorToken);
763
+ transport$1.notifySubscription = this._monitorCharacteristic(transport$1.notifyCharacteristic, uuid, monitorToken, notifyTransactionId);
764
+ transportCache[uuid] = transport$1;
765
+ this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
766
+ this.protocolV2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
767
+ if (reactNative.Platform.OS === 'ios') {
768
+ yield new Promise(resolve => {
769
+ setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
770
+ });
771
+ }
772
+ else if (reactNative.Platform.OS === 'android') {
773
+ yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
774
+ }
775
+ const initialMtu = transport$1.mtuSize;
776
+ let refreshAttempts = 0;
777
+ if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
778
+ (typeof transport$1.mtuSize !== 'number' || transport$1.mtuSize < BLE_MTU_REFRESH_THRESHOLD)) {
779
+ refreshAttempts += 1;
780
+ let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
781
+ transport$1.device = refreshedDevice;
782
+ transport$1.mtuSize =
783
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
784
+ if (typeof transport$1.mtuSize !== 'number' || transport$1.mtuSize < BLE_MTU_REFRESH_THRESHOLD) {
785
+ yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
786
+ refreshAttempts += 1;
787
+ refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
788
+ transport$1.device = refreshedDevice;
789
+ transport$1.mtuSize =
790
+ typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
791
+ }
792
+ }
793
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
794
+ platform: reactNative.Platform.OS,
795
+ requested: getRequestedBleMtu(),
796
+ initial: initialMtu,
797
+ actual: transport$1.mtuSize,
798
+ refreshAttempts,
799
+ });
800
+ return transport$1;
801
+ });
802
+ }
706
803
  acquire(input) {
707
804
  var _a, _b;
708
805
  return __awaiter(this, void 0, void 0, function* () {
@@ -727,9 +824,8 @@ class ReactNativeBleTransport {
727
824
  if (forceCleanRunPromise && this.runPromise) {
728
825
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
729
826
  this.runPromise.reject(error);
730
- this.rejectAllProtocolV2Frames(error);
731
827
  this.runPromise = null;
732
- this.activeProtocolV2Call = null;
828
+ this.runPromiseDeviceId = null;
733
829
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
734
830
  }
735
831
  const blePlxManager = yield this.getPlxManager();
@@ -762,14 +858,17 @@ class ReactNativeBleTransport {
762
858
  if (!device) {
763
859
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
764
860
  try {
765
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
861
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
766
862
  }
767
863
  catch (e) {
768
864
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
865
+ if (isConnectTimeoutError(e)) {
866
+ throw e;
867
+ }
769
868
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
770
869
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
771
870
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
772
- device = yield blePlxManager.connectToDevice(uuid);
871
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
773
872
  }
774
873
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
775
874
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -785,23 +884,27 @@ class ReactNativeBleTransport {
785
884
  }
786
885
  if (!(yield device.isConnected())) {
787
886
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
887
+ const disconnectedDevice = device;
788
888
  try {
789
- device = yield device.connect(connectOptions);
889
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
790
890
  }
791
891
  catch (e) {
792
892
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
893
+ if (isConnectTimeoutError(e)) {
894
+ throw e;
895
+ }
793
896
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
794
897
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
795
898
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
796
899
  try {
797
- device = yield device.connect();
900
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
798
901
  }
799
902
  catch (e) {
800
903
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
801
904
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
802
905
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
803
- yield device.cancelConnection();
804
- device = yield device.connect();
906
+ yield disconnectedDevice.cancelConnection();
907
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
805
908
  }
806
909
  }
807
910
  }
@@ -810,51 +913,42 @@ class ReactNativeBleTransport {
810
913
  }
811
914
  }
812
915
  }
813
- device = yield requestAndroidMtu(device);
814
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
916
+ device = yield resolveNegotiatedMtu(device);
917
+ const acquiredDevice = device;
918
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
815
919
  const protocolHint = expectedProtocol
816
920
  ? undefined
817
- : (_a = this.deviceProtocolHints.get(uuid)) !== null && _a !== void 0 ? _a : inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
921
+ : (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
818
922
  yield this.release(uuid, true);
819
923
  if (protocolHint) {
820
924
  this.deviceProtocolHints.set(uuid, protocolHint);
821
925
  }
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
- });
926
+ yield this.installTransportForAcquire(uuid, acquiredDevice, {
927
+ writeCharacteristic,
928
+ notifyCharacteristic,
929
+ });
930
+ try {
931
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
932
+ yield this.installTransportForAcquire(uuid, acquiredDevice);
933
+ }));
934
+ const currentTransport = transportCache[uuid];
935
+ if (!currentTransport) {
936
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
937
+ }
938
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
939
+ return { uuid, protocolType };
839
940
  }
840
- else if (reactNative.Platform.OS === 'android') {
841
- yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
941
+ catch (error) {
942
+ yield this.release(uuid, true);
943
+ throw error;
842
944
  }
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
945
  });
852
946
  }
853
947
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
854
948
  let bufferLength = 0;
855
949
  let buffer$1 = [];
856
950
  const subscription = characteristic.monitor((error, c) => {
857
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
951
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
858
952
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
859
953
  if (error) {
860
954
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -866,28 +960,44 @@ class ReactNativeBleTransport {
866
960
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
867
961
  return;
868
962
  }
869
- if (this.runPromise) {
870
- let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
963
+ if (this.getActiveProtocol(uuid) === 'V2') {
964
+ let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
871
965
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
872
- ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
966
+ errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
873
967
  }
874
- if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
875
- ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
968
+ else if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
969
+ errorCode = hdShared.HardwareErrorCode.BleDeviceBondError;
876
970
  }
877
- if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
971
+ else if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
878
972
  ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('Cannot find client characteristic config descriptor')) ||
879
973
  ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('The handle is invalid')) ||
880
974
  ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('Writing is not permitted')) ||
881
975
  ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('notify change failed for device'))) {
976
+ errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
977
+ }
978
+ this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
979
+ return;
980
+ }
981
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
982
+ let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
983
+ if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
984
+ ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
985
+ }
986
+ if ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Encryption is insufficient')) {
987
+ ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
988
+ }
989
+ if (((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('Cannot write client characteristic config descriptor')) ||
990
+ ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Cannot find client characteristic config descriptor')) ||
991
+ ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('The handle is invalid')) ||
992
+ ((_o = error.reason) === null || _o === void 0 ? void 0 : _o.includes('Writing is not permitted')) ||
993
+ ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
882
994
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
883
995
  this.runPromise.reject(notifyError);
884
- this.rejectAllProtocolV2Frames(notifyError);
885
996
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
886
997
  return;
887
998
  }
888
999
  const notifyError = hdShared.ERRORS.TypedError(ERROR);
889
1000
  this.runPromise.reject(notifyError);
890
- this.rejectAllProtocolV2Frames(notifyError);
891
1001
  Log === null || Log === void 0 ? void 0 : Log.debug(': monitor notify error, and has unreleased Promise', Error);
892
1002
  }
893
1003
  return;
@@ -901,13 +1011,13 @@ class ReactNativeBleTransport {
901
1011
  }
902
1012
  try {
903
1013
  const data = buffer.Buffer.from(c.value, 'base64');
904
- const protocol = this.deviceProtocol.get(uuid);
1014
+ const protocol = this.getActiveProtocol(uuid);
905
1015
  if (!protocol) {
906
1016
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
907
1017
  return;
908
1018
  }
909
1019
  if (protocol === 'V2') {
910
- this.handleProtocolV2Notification(uuid, new Uint8Array(data));
1020
+ this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
911
1021
  return;
912
1022
  }
913
1023
  if (isHeaderChunk(data)) {
@@ -921,28 +1031,40 @@ class ReactNativeBleTransport {
921
1031
  const value = buffer.Buffer.from(buffer$1);
922
1032
  bufferLength = 0;
923
1033
  buffer$1 = [];
924
- (_h = this.runPromise) === null || _h === void 0 ? void 0 : _h.resolve(value.toString('hex'));
1034
+ if (this.runPromiseDeviceId === uuid) {
1035
+ (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1036
+ }
925
1037
  }
926
1038
  }
927
1039
  catch (error) {
928
1040
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
929
1041
  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);
1042
+ if (this.getActiveProtocol(uuid) === 'V2') {
1043
+ this.rejectProtocolV2Frames(uuid, notifyError);
1044
+ }
1045
+ else if (this.runPromiseDeviceId === uuid) {
1046
+ (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
1047
+ }
932
1048
  }
933
1049
  }, notifyTransactionId);
934
1050
  return subscription;
935
1051
  }
936
1052
  release(uuid, onclose = false) {
937
- var _a, _b, _c, _d, _e, _f, _g, _h;
1053
+ return __awaiter(this, void 0, void 0, function* () {
1054
+ yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
1055
+ return this.releaseNative(uuid, onclose);
1056
+ });
1057
+ }
1058
+ releaseNative(uuid, onclose = false) {
1059
+ var _a, _b, _c, _d, _e, _f, _g;
938
1060
  return __awaiter(this, void 0, void 0, function* () {
939
1061
  const transport = transportCache[uuid];
940
- if (this.runPromise) {
1062
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
941
1063
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
942
1064
  this.runPromise.reject(error);
943
1065
  this.runPromise = null;
944
- this.rejectAllProtocolV2Frames(error);
945
- this.activeProtocolV2Call = null;
1066
+ this.runPromiseDeviceId = null;
1067
+ this.rejectProtocolV2Frames(uuid, error);
946
1068
  }
947
1069
  else {
948
1070
  this.resetProtocolV2Frames(uuid);
@@ -950,24 +1072,22 @@ class ReactNativeBleTransport {
950
1072
  if (reactNative.Platform.OS === 'android' && !onclose && transport) {
951
1073
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
952
1074
  this.resetProtocolV2Frames(uuid);
953
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
954
- this.activeProtocolV2Call = null;
955
- }
956
1075
  return Promise.resolve(true);
957
1076
  }
1077
+ yield this.restoreAndroidConnectionPriority(uuid, transport);
958
1078
  if (transport) {
959
1079
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
960
1080
  this.monitorTokens.delete(uuid);
961
1081
  }
962
1082
  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();
1083
+ (_b = transport.disconnectSubscription) === null || _b === void 0 ? void 0 : _b.remove();
964
1084
  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();
1085
+ 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);
1086
+ (_d = transport.notifySubscription) === null || _d === void 0 ? void 0 : _d.remove();
967
1087
  transport.notifySubscription = undefined;
968
1088
  if (transport.notifyTransactionId) {
969
1089
  try {
970
- yield ((_f = this.blePlxManager) === null || _f === void 0 ? void 0 : _f.cancelTransaction(transport.notifyTransactionId));
1090
+ yield ((_e = this.blePlxManager) === null || _e === void 0 ? void 0 : _e.cancelTransaction(transport.notifyTransactionId));
971
1091
  }
972
1092
  catch (e) {
973
1093
  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 +1095,14 @@ class ReactNativeBleTransport {
975
1095
  }
976
1096
  delete transportCache[uuid];
977
1097
  }
1098
+ this.protocolV2HighVolumeLogSignatures.delete(uuid);
978
1099
  this.deviceProtocol.delete(uuid);
979
- this.deviceProtocolHints.delete(uuid);
980
- (_g = this.protocolV2Assemblers.get(uuid)) === null || _g === void 0 ? void 0 : _g.reset();
1100
+ this.probingProtocols.delete(uuid);
1101
+ (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
981
1102
  this.protocolV2Assemblers.delete(uuid);
982
1103
  this.resetProtocolV2Frames(uuid);
983
1104
  try {
984
- yield ((_h = this.blePlxManager) === null || _h === void 0 ? void 0 : _h.cancelTransaction(uuid));
1105
+ yield ((_g = this.blePlxManager) === null || _g === void 0 ? void 0 : _g.cancelTransaction(uuid));
985
1106
  }
986
1107
  catch (e) {
987
1108
  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 +1123,18 @@ class ReactNativeBleTransport {
1002
1123
  if (this._messages == null) {
1003
1124
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1004
1125
  }
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
1126
  const protocol = this.getProtocolType(uuid);
1011
1127
  if (!protocol) {
1012
1128
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1013
1129
  }
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
- }
1130
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport call', transport.createTransportCallLog(name, protocol, data));
1026
1131
  if (protocol === 'V2') {
1027
1132
  return this.callProtocolV2(uuid, name, data, options);
1028
1133
  }
1134
+ const forceRun = name === 'Initialize' || name === 'Cancel';
1135
+ if (this.runPromise && !forceRun) {
1136
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1137
+ }
1029
1138
  return this.callProtocolV1(uuid, name, data, options);
1030
1139
  });
1031
1140
  }
@@ -1037,7 +1146,19 @@ class ReactNativeBleTransport {
1037
1146
  const transport = this.getCachedTransport(uuid);
1038
1147
  const runPromise = hdShared.createDeferred();
1039
1148
  runPromise.promise.catch(() => undefined);
1149
+ const supersededRunPromise = this.runPromise;
1150
+ if (supersededRunPromise) {
1151
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1152
+ }
1040
1153
  this.runPromise = runPromise;
1154
+ this.runPromiseDeviceId = uuid;
1155
+ const releaseOwnershipIfCurrent = () => {
1156
+ if (this.runPromise === runPromise) {
1157
+ this.runPromise = null;
1158
+ this.runPromiseDeviceId = null;
1159
+ }
1160
+ };
1161
+ const isCurrentOwner = () => this.runPromise === runPromise;
1041
1162
  const messages = this._messages;
1042
1163
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1043
1164
  let timeout;
@@ -1058,6 +1179,9 @@ class ReactNativeBleTransport {
1058
1179
  }
1059
1180
  catch (e) {
1060
1181
  onError(e);
1182
+ if (isWedgedWriteError(e)) {
1183
+ throw e;
1184
+ }
1061
1185
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1062
1186
  }
1063
1187
  }
@@ -1085,6 +1209,9 @@ class ReactNativeBleTransport {
1085
1209
  }
1086
1210
  catch (e) {
1087
1211
  onError(e);
1212
+ if (isWedgedWriteError(e)) {
1213
+ throw e;
1214
+ }
1088
1215
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1089
1216
  }
1090
1217
  }
@@ -1095,13 +1222,13 @@ class ReactNativeBleTransport {
1095
1222
  });
1096
1223
  }
1097
1224
  if (name === 'EmmcFileWrite') {
1098
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1099
- this.runPromise = null;
1225
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1226
+ releaseOwnershipIfCurrent();
1100
1227
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1101
1228
  });
1102
1229
  }
1103
1230
  else if (name === 'FirmwareUpload') {
1104
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
1231
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
1105
1232
  packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
1106
1233
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1107
1234
  pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
@@ -1112,7 +1239,7 @@ class ReactNativeBleTransport {
1112
1239
  let attempt = 0;
1113
1240
  while (true) {
1114
1241
  try {
1115
- yield transport.writeCharacteristic.writeWithoutResponse(data);
1242
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1116
1243
  return;
1117
1244
  }
1118
1245
  catch (error) {
@@ -1120,36 +1247,18 @@ class ReactNativeBleTransport {
1120
1247
  if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1121
1248
  throw error;
1122
1249
  }
1123
- const shouldReconnect = retryType === 'reconnectable';
1124
- const delayMs = shouldReconnect
1125
- ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
1126
- : resolveFirmwareUploadRetryDelay(attempt);
1250
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1127
1251
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
1128
1252
  attempt: attempt + 1,
1129
1253
  delayMs,
1130
- reconnect: shouldReconnect,
1131
1254
  error,
1132
1255
  });
1133
- if (shouldReconnect) {
1134
- this.firmwareUploadWriteRecoveryIds.add(uuid);
1135
- }
1136
1256
  yield delay(delayMs);
1137
1257
  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
1258
  }
1150
1259
  }
1151
1260
  }), e => {
1152
- this.runPromise = null;
1261
+ releaseOwnershipIfCurrent();
1153
1262
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1154
1263
  });
1155
1264
  }
@@ -1157,11 +1266,17 @@ class ReactNativeBleTransport {
1157
1266
  for (const o of buffers) {
1158
1267
  const outData = o.toString('base64');
1159
1268
  try {
1160
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1269
+ const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1270
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1271
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1272
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1161
1273
  }
1162
1274
  catch (e) {
1163
1275
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1164
- this.runPromise = null;
1276
+ releaseOwnershipIfCurrent();
1277
+ if (isWedgedWriteError(e)) {
1278
+ throw e;
1279
+ }
1165
1280
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1166
1281
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1167
1282
  }
@@ -1190,17 +1305,23 @@ class ReactNativeBleTransport {
1190
1305
  if (typeof response !== 'string') {
1191
1306
  throw new Error('Returning data is not string.');
1192
1307
  }
1193
- Log === null || Log === void 0 ? void 0 : Log.debug('receive data: ', response);
1194
1308
  const jsonData = ProtocolV1.decodeMessage(messages, response);
1195
1309
  return check.call(jsonData);
1196
1310
  }
1197
1311
  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);
1312
+ if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1313
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1200
1314
  }
1201
1315
  else {
1202
1316
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1203
1317
  }
1318
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1319
+ const isStaleCall = this.runPromise !== runPromise;
1320
+ if (!isProbeTimeout &&
1321
+ !isStaleCall &&
1322
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1323
+ yield this.disconnect(uuid);
1324
+ }
1204
1325
  throw e;
1205
1326
  }
1206
1327
  finally {
@@ -1208,6 +1329,7 @@ class ReactNativeBleTransport {
1208
1329
  clearTimeout(timeout);
1209
1330
  if (this.runPromise === runPromise) {
1210
1331
  this.runPromise = null;
1332
+ this.runPromiseDeviceId = null;
1211
1333
  }
1212
1334
  }
1213
1335
  });
@@ -1216,10 +1338,11 @@ class ReactNativeBleTransport {
1216
1338
  this.stopped = true;
1217
1339
  }
1218
1340
  disconnect(session) {
1219
- var _a, _b, _c, _d, _e, _f;
1341
+ var _a, _b, _c, _d, _e;
1220
1342
  return __awaiter(this, void 0, void 0, function* () {
1221
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport resetSession: ', session);
1343
+ yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
1222
1344
  const transport = transportCache[session];
1345
+ const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
1223
1346
  if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
1224
1347
  try {
1225
1348
  Log === null || Log === void 0 ? void 0 : Log.debug('disconnect: removing disconnect subscription');
@@ -1232,7 +1355,7 @@ class ReactNativeBleTransport {
1232
1355
  }
1233
1356
  if (transport === null || transport === void 0 ? void 0 : transport.notifySubscription) {
1234
1357
  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);
1358
+ 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
1359
  transport.notifySubscription.remove();
1237
1360
  transport.notifySubscription = undefined;
1238
1361
  }
@@ -1242,7 +1365,7 @@ class ReactNativeBleTransport {
1242
1365
  }
1243
1366
  if (session) {
1244
1367
  try {
1245
- yield ((_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelTransaction(session));
1368
+ yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(session));
1246
1369
  }
1247
1370
  catch (e) {
1248
1371
  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 +1380,7 @@ class ReactNativeBleTransport {
1257
1380
  }
1258
1381
  }
1259
1382
  try {
1260
- yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelDeviceConnection(session));
1383
+ yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelDeviceConnection(session));
1261
1384
  }
1262
1385
  catch (e) {
1263
1386
  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 +1389,21 @@ class ReactNativeBleTransport {
1266
1389
  delete transportCache[session];
1267
1390
  }
1268
1391
  this.deviceProtocol.delete(session);
1392
+ this.probingProtocols.delete(session);
1269
1393
  this.deviceProtocolHints.delete(session);
1394
+ this.sessionProtocols.delete(session);
1395
+ this.protocolReprobeFailures.delete(session);
1270
1396
  this.protocolV2Assemblers.delete(session);
1271
1397
  this.resetProtocolV2Frames(session);
1272
- if (((_d = this.activeProtocolV2Call) === null || _d === void 0 ? void 0 : _d.uuid) === session) {
1273
- this.activeProtocolV2Call = null;
1274
- }
1275
1398
  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
- });
1399
+ this.emitDeviceDisconnect(session, (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name, monitorToken);
1281
1400
  }
1282
1401
  catch (e) {
1283
1402
  Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
1284
1403
  }
1404
+ if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
1405
+ this.monitorTokens.delete(session);
1406
+ }
1285
1407
  yield new Promise(resolve => setTimeout(() => resolve(), 100));
1286
1408
  });
1287
1409
  }
@@ -1289,6 +1411,92 @@ class ReactNativeBleTransport {
1289
1411
  Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1290
1412
  if (this.runPromise) ;
1291
1413
  this.runPromise = null;
1414
+ this.runPromiseDeviceId = null;
1415
+ }
1416
+ connectWithTimeout(uuid, connect) {
1417
+ return __awaiter(this, void 0, void 0, function* () {
1418
+ let timer;
1419
+ let timedOut = false;
1420
+ const pending = connect();
1421
+ pending.catch(() => undefined);
1422
+ try {
1423
+ const result = yield Promise.race([
1424
+ pending,
1425
+ new Promise((_, reject) => {
1426
+ timer = setTimeout(() => {
1427
+ timedOut = true;
1428
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1429
+ }, BLE_CONNECT_TIMEOUT_MS);
1430
+ }),
1431
+ ]);
1432
+ return result;
1433
+ }
1434
+ catch (error) {
1435
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1436
+ this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1437
+ }
1438
+ throw error;
1439
+ }
1440
+ finally {
1441
+ if (timer)
1442
+ clearTimeout(timer);
1443
+ }
1444
+ });
1445
+ }
1446
+ resolveCharacteristicsWithTimeout(uuid, device) {
1447
+ return __awaiter(this, void 0, void 0, function* () {
1448
+ let timer;
1449
+ let timedOut = false;
1450
+ const pending = this.resolveCharacteristics(device);
1451
+ pending.catch(() => undefined);
1452
+ try {
1453
+ const result = yield Promise.race([
1454
+ pending,
1455
+ new Promise((_, reject) => {
1456
+ timer = setTimeout(() => {
1457
+ timedOut = true;
1458
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1459
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1460
+ }),
1461
+ ]);
1462
+ this.connectionSetupTimeoutCounts.delete(uuid);
1463
+ return result;
1464
+ }
1465
+ catch (error) {
1466
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1467
+ this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1468
+ }
1469
+ throw error;
1470
+ }
1471
+ finally {
1472
+ if (timer)
1473
+ clearTimeout(timer);
1474
+ }
1475
+ });
1476
+ }
1477
+ abandonStalledConnection(uuid, stage) {
1478
+ var _a, _b;
1479
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1480
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1481
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1482
+ stage,
1483
+ setupTimeoutsSinceSuccess: timeouts,
1484
+ });
1485
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1486
+ });
1487
+ const stalled = transportCache[uuid];
1488
+ if (stalled) {
1489
+ delete transportCache[uuid];
1490
+ }
1491
+ this.deviceProtocol.delete(uuid);
1492
+ this.probingProtocols.delete(uuid);
1493
+ this.protocolV2Assemblers.delete(uuid);
1494
+ this.resetProtocolV2Frames(uuid);
1495
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1496
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1497
+ this.resetPlxManager();
1498
+ this.connectionSetupTimeoutCounts.delete(uuid);
1499
+ }
1292
1500
  }
1293
1501
  getCachedTransport(uuid) {
1294
1502
  const transport = transportCache[uuid];
@@ -1297,58 +1505,187 @@ class ReactNativeBleTransport {
1297
1505
  }
1298
1506
  return transport;
1299
1507
  }
1508
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1509
+ return __awaiter(this, void 0, void 0, function* () {
1510
+ let timer;
1511
+ let timedOut = false;
1512
+ try {
1513
+ yield Promise.race([
1514
+ write(data),
1515
+ new Promise((_, reject) => {
1516
+ timer = setTimeout(() => {
1517
+ timedOut = true;
1518
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1519
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1520
+ }),
1521
+ ]);
1522
+ this.writeTimeoutCounts.delete(uuid);
1523
+ }
1524
+ catch (error) {
1525
+ if (timedOut) {
1526
+ if (isCurrentOwner && !isCurrentOwner()) {
1527
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1528
+ }
1529
+ else {
1530
+ this.tearDownWedgedLink(uuid);
1531
+ }
1532
+ }
1533
+ throw error;
1534
+ }
1535
+ finally {
1536
+ if (timer)
1537
+ clearTimeout(timer);
1538
+ }
1539
+ });
1540
+ }
1541
+ tearDownWedgedLink(uuid) {
1542
+ var _a;
1543
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1544
+ this.writeTimeoutCounts.set(uuid, timeouts);
1545
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1546
+ consecutiveWriteTimeouts: timeouts,
1547
+ });
1548
+ const wedged = transportCache[uuid];
1549
+ this.disconnect(uuid).catch(error => {
1550
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1551
+ });
1552
+ if (wedged && transportCache[uuid] === wedged) {
1553
+ delete transportCache[uuid];
1554
+ }
1555
+ this.deviceProtocol.delete(uuid);
1556
+ this.probingProtocols.delete(uuid);
1557
+ this.protocolV2Assemblers.delete(uuid);
1558
+ this.resetProtocolV2Frames(uuid);
1559
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1560
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1561
+ this.resetPlxManager();
1562
+ this.writeTimeoutCounts.delete(uuid);
1563
+ }
1564
+ }
1565
+ resetPlxManager() {
1566
+ const manager = this.blePlxManager;
1567
+ this.blePlxManager = undefined;
1568
+ Object.keys(transportCache).forEach(key => {
1569
+ delete transportCache[key];
1570
+ });
1571
+ this.deviceProtocol.clear();
1572
+ this.probingProtocols.clear();
1573
+ this.sessionProtocols.clear();
1574
+ this.protocolReprobeFailures.clear();
1575
+ this.monitorTokens.clear();
1576
+ this.protocolV2Assemblers.clear();
1577
+ try {
1578
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1579
+ }
1580
+ catch (error) {
1581
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1582
+ }
1583
+ }
1300
1584
  createProtocolMismatchError(expected) {
1301
1585
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1302
1586
  }
1303
1587
  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');
1588
+ 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
1589
  }
1306
1590
  clearProbeProtocol(uuid, protocol) {
1591
+ if (this.probingProtocols.get(uuid) === protocol) {
1592
+ this.probingProtocols.delete(uuid);
1593
+ }
1307
1594
  if (this.deviceProtocol.get(uuid) === protocol) {
1308
1595
  this.deviceProtocol.delete(uuid);
1309
1596
  }
1310
1597
  }
1311
- detectProtocol(uuid, expectedProtocol, protocolHint) {
1598
+ getActiveProtocol(uuid) {
1599
+ var _a;
1600
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1601
+ }
1602
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1603
+ var _a;
1312
1604
  return __awaiter(this, void 0, void 0, function* () {
1605
+ if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
1606
+ this.deviceProtocol.set(uuid, expectedProtocol);
1607
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1608
+ deviceId: uuid,
1609
+ protocol: expectedProtocol,
1610
+ source: 'expected',
1611
+ });
1612
+ return expectedProtocol;
1613
+ }
1313
1614
  if (expectedProtocol === 'V1') {
1314
1615
  if (yield this.probeProtocolV1(uuid)) {
1315
1616
  this.deviceProtocol.set(uuid, 'V1');
1316
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
1617
+ this.sessionProtocols.set(uuid, 'V1');
1618
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1619
+ deviceId: uuid,
1620
+ protocol: 'V1',
1621
+ source: 'expected',
1622
+ });
1317
1623
  return 'V1';
1318
1624
  }
1319
1625
  throw this.createProtocolMismatchError(expectedProtocol);
1320
1626
  }
1321
1627
  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';
1628
+ if (yield this.probeProtocolV2(uuid)) {
1629
+ this.deviceProtocol.set(uuid, 'V2');
1630
+ this.sessionProtocols.set(uuid, 'V2');
1631
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1632
+ deviceId: uuid,
1633
+ protocol: 'V2',
1634
+ source: 'expected',
1635
+ });
1636
+ return 'V2';
1637
+ }
1638
+ throw this.createProtocolMismatchError(expectedProtocol);
1325
1639
  }
1326
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1640
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1641
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1642
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1643
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1644
+ !protocolHint &&
1645
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1646
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1327
1647
  for (let i = 0; i < probeOrder.length; i += 1) {
1328
1648
  const protocol = probeOrder[i];
1329
1649
  if (i > 0) {
1330
1650
  yield this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1651
+ if (!transportCache[uuid]) {
1652
+ if (!rebuildTransport) {
1653
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1654
+ }
1655
+ yield rebuildTransport();
1656
+ }
1331
1657
  }
1332
1658
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1333
1659
  if (detected) {
1334
1660
  this.deviceProtocol.set(uuid, protocol);
1335
- Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
1661
+ this.sessionProtocols.set(uuid, protocol);
1662
+ this.protocolReprobeFailures.delete(uuid);
1663
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1664
+ deviceId: uuid,
1665
+ protocol,
1666
+ source: 'probe',
1667
+ });
1336
1668
  return protocol;
1337
1669
  }
1338
1670
  }
1671
+ if (trustSessionProtocol) {
1672
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1673
+ }
1674
+ else {
1675
+ this.protocolReprobeFailures.delete(uuid);
1676
+ }
1339
1677
  this.deviceProtocol.delete(uuid);
1678
+ this.probingProtocols.delete(uuid);
1340
1679
  throw this.createProtocolDetectionError();
1341
1680
  });
1342
1681
  }
1343
1682
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1344
- var _a, _b, _c, _d;
1683
+ var _a, _b, _c;
1345
1684
  return __awaiter(this, void 0, void 0, function* () {
1346
1685
  const transport = transportCache[uuid];
1686
+ yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1347
1687
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1348
1688
  this.resetProtocolV2Frames(uuid);
1349
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
1350
- this.activeProtocolV2Call = null;
1351
- }
1352
1689
  if (this.runPromise) {
1353
1690
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1354
1691
  this.runPromise.reject(error);
@@ -1360,11 +1697,11 @@ class ReactNativeBleTransport {
1360
1697
  if (this.monitorTokens.get(uuid) === transport.monitorToken) {
1361
1698
  this.monitorTokens.delete(uuid);
1362
1699
  }
1363
- (_c = transport.notifySubscription) === null || _c === void 0 ? void 0 : _c.remove();
1700
+ (_b = transport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
1364
1701
  transport.notifySubscription = undefined;
1365
1702
  if (previousNotifyTransactionId) {
1366
1703
  try {
1367
- yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelTransaction(previousNotifyTransactionId));
1704
+ yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(previousNotifyTransactionId));
1368
1705
  }
1369
1706
  catch (error) {
1370
1707
  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 +1727,17 @@ class ReactNativeBleTransport {
1390
1727
  return false;
1391
1728
  }
1392
1729
  try {
1393
- this.deviceProtocol.set(uuid, 'V1');
1394
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1730
+ this.probingProtocols.set(uuid, 'V1');
1731
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1732
+ this.probingProtocols.delete(uuid);
1395
1733
  return true;
1396
1734
  }
1397
1735
  catch (error) {
1398
1736
  this.clearProbeProtocol(uuid, 'V1');
1399
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1737
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1738
+ if (isWedgedWriteError(error)) {
1739
+ throw error;
1740
+ }
1400
1741
  return false;
1401
1742
  }
1402
1743
  });
@@ -1407,7 +1748,7 @@ class ReactNativeBleTransport {
1407
1748
  if (!this._messages || !this._messagesV2) {
1408
1749
  return false;
1409
1750
  }
1410
- this.deviceProtocol.set(uuid, 'V2');
1751
+ this.probingProtocols.set(uuid, 'V2');
1411
1752
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1412
1753
  const detected = yield transport.probeProtocolV2({
1413
1754
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1423,17 +1764,16 @@ class ReactNativeBleTransport {
1423
1764
  if (!detected) {
1424
1765
  this.clearProbeProtocol(uuid, 'V2');
1425
1766
  }
1767
+ else {
1768
+ this.probingProtocols.delete(uuid);
1769
+ }
1426
1770
  return detected;
1427
1771
  });
1428
1772
  }
1429
- handleProtocolV2Notification(uuid, data) {
1430
- var _a, _b, _c;
1773
+ handleProtocolV2Notification(uuid, monitorToken, data) {
1431
1774
  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);
1775
+ if (this.monitorTokens.get(uuid) !== monitorToken)
1435
1776
  return;
1436
- }
1437
1777
  if (data.length === 0)
1438
1778
  return;
1439
1779
  const assembler = this.protocolV2Assemblers.get(uuid);
@@ -1446,8 +1786,10 @@ class ReactNativeBleTransport {
1446
1786
  catch (error) {
1447
1787
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
1448
1788
  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);
1789
+ this.rejectProtocolV2Frames(uuid, notifyError);
1790
+ this.protocolV2Links
1791
+ .invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
1792
+ .catch(invalidateError => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 notify cleanup failed:', invalidateError));
1451
1793
  }
1452
1794
  }
1453
1795
  getProtocolV2FrameQueue(uuid) {
@@ -1467,20 +1809,16 @@ class ReactNativeBleTransport {
1467
1809
  }
1468
1810
  this.getProtocolV2FrameQueue(uuid).push(frame);
1469
1811
  }
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
1812
  resetProtocolV2Frames(uuid) {
1478
- this.protocolV2FrameQueues.delete(uuid);
1479
- this.protocolV2FramePromises.delete(uuid);
1813
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1480
1814
  }
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;
1815
+ rejectProtocolV2Frames(uuid, error) {
1816
+ this.protocolV2FrameQueues.delete(uuid);
1817
+ const framePromise = this.protocolV2FramePromises.get(uuid);
1818
+ if (framePromise) {
1819
+ this.protocolV2FramePromises.delete(uuid);
1820
+ framePromise.reject(error);
1821
+ }
1484
1822
  }
1485
1823
  readProtocolV2Frame(uuid) {
1486
1824
  return __awaiter(this, void 0, void 0, function* () {
@@ -1500,133 +1838,240 @@ class ReactNativeBleTransport {
1500
1838
  }
1501
1839
  });
1502
1840
  }
1503
- writeProtocolV2Frame(transport, frame, options) {
1841
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1504
1842
  return __awaiter(this, void 0, void 0, function* () {
1505
- const tuning = getProtocolV2BleTuning();
1506
- const packetCapacity = resolveProtocolV2PacketCapacity({
1843
+ const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
1507
1844
  platform: reactNative.Platform.OS,
1508
- iosPacketLength: tuning.iosPacketLength,
1509
- androidPacketLength: tuning.androidPacketLength,
1510
- mtu: reactNative.Platform.OS === 'android' ? transport.mtuSize : undefined,
1845
+ highVolume: context.highVolume,
1846
+ requestedWithResponse: context.writeWithResponse,
1847
+ characteristic: transport.writeCharacteristic,
1511
1848
  });
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
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1517
- const chunk = frame.slice(offset, offset + packetCapacity);
1518
- const base64 = buffer.Buffer.from(chunk).toString('base64');
1519
- if (writeMode === 'withResponse') {
1520
- yield transport.writeCharacteristic.writeWithResponse(base64);
1849
+ let attempt = 0;
1850
+ for (;;) {
1851
+ assertCurrentGeneration();
1852
+ if (context.signal.aborted) {
1853
+ throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1521
1854
  }
1522
- else {
1523
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1855
+ try {
1856
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
1857
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1858
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1859
+ try {
1860
+ assertCurrentGeneration();
1861
+ return !context.signal.aborted;
1862
+ }
1863
+ catch (_a) {
1864
+ return false;
1865
+ }
1866
+ });
1867
+ assertCurrentGeneration();
1868
+ return;
1524
1869
  }
1525
- packetsWritten += 1;
1526
- if (shouldThrottle &&
1527
- packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
1528
- offset + packetCapacity < frame.length) {
1529
- yield delay(tuning.highVolumeWritePauseMs);
1870
+ catch (error) {
1871
+ if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
1872
+ attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1873
+ throw error;
1874
+ }
1875
+ const delayMs = resolveFirmwareUploadRetryDelay(attempt);
1876
+ attempt += 1;
1877
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
1878
+ name: context.messageName,
1879
+ attempt,
1880
+ delayMs,
1881
+ });
1882
+ yield delay(delayMs);
1530
1883
  }
1531
1884
  }
1532
- if (shouldThrottle) {
1533
- yield delay(tuning.highVolumeWriteFlushDelayMs);
1534
- }
1885
+ });
1886
+ }
1887
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1888
+ return __awaiter(this, void 0, void 0, function* () {
1889
+ const tuning = getProtocolV2BleTuning();
1890
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1891
+ platform: reactNative.Platform.OS,
1892
+ iosPacketLength: tuning.iosPacketLength,
1893
+ androidPacketLength: tuning.androidPacketLength,
1894
+ mtu: transport$1.mtuSize,
1895
+ });
1896
+ yield transport.writeProtocolV2BleFrame({
1897
+ frame,
1898
+ packetCapacity,
1899
+ assertActive: assertCurrentGeneration,
1900
+ signal: context.signal,
1901
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1902
+ wait: delay,
1903
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1904
+ });
1535
1905
  });
1536
1906
  }
1537
1907
  callProtocolV2(uuid, name, data, options) {
1538
- var _a, _b, _c, _d;
1908
+ var _a;
1539
1909
  return __awaiter(this, void 0, void 0, function* () {
1540
1910
  if (!this._messages || !this._messagesV2) {
1541
1911
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1542
1912
  }
1543
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
1544
- if (this.runPromise) {
1545
- if (!forceRun) {
1546
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1547
- }
1548
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1549
- this.runPromise.reject(error);
1550
- this.rejectAllProtocolV2Frames(error);
1551
- this.runPromise = null;
1552
- this.activeProtocolV2Call = null;
1553
- }
1554
- const transport$1 = this.getCachedTransport(uuid);
1555
- const runPromise = hdShared.createDeferred();
1556
- runPromise.promise.catch(() => undefined);
1557
- this.runPromise = runPromise;
1558
- const callToken = this.nextProtocolV2CallToken++;
1559
- this.activeProtocolV2Call = { uuid, token: callToken };
1560
- (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1561
- this.resetProtocolV2Frames(uuid);
1562
- let completed = false;
1563
- 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 });
1913
+ const callOptions = options;
1564
1914
  const highVolumeWrite = transport.LogBlockCommand.has(name);
1565
1915
  if (highVolumeWrite) {
1566
1916
  const tuning = getProtocolV2BleTuning();
1567
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:', name, {
1568
- packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1569
- burstSize: tuning.highVolumeWriteBurstSize,
1570
- pauseMs: tuning.highVolumeWritePauseMs,
1571
- flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
1572
- writeWithResponse: tuning.highVolumeWriteWithResponse,
1917
+ const currentTransport = this.getCachedTransport(uuid);
1918
+ const writeWithResponse = shouldWriteProtocolV2WithResponse({
1919
+ platform: reactNative.Platform.OS,
1920
+ highVolume: true,
1921
+ requestedWithResponse: options === null || options === void 0 ? void 0 : options.writeWithResponse,
1922
+ characteristic: currentTransport.writeCharacteristic,
1923
+ });
1924
+ const packetCapacity = resolveProtocolV2PacketCapacity({
1925
+ platform: reactNative.Platform.OS,
1926
+ iosPacketLength: tuning.iosPacketLength,
1927
+ androidPacketLength: tuning.androidPacketLength,
1928
+ mtu: currentTransport.mtuSize,
1573
1929
  });
1930
+ const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
1931
+ const logSignature = `${name}:${writeMode}:${String(currentTransport.mtuSize)}:${packetCapacity}`;
1932
+ const loggedSignatures = (_a = this.protocolV2HighVolumeLogSignatures.get(uuid)) !== null && _a !== void 0 ? _a : new Set();
1933
+ if (!loggedSignatures.has(logSignature)) {
1934
+ loggedSignatures.add(logSignature);
1935
+ this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
1936
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1937
+ name,
1938
+ writeMode,
1939
+ reportedMtu: currentTransport.mtuSize,
1940
+ packetCapacity,
1941
+ });
1942
+ }
1943
+ }
1944
+ if (highVolumeWrite) {
1945
+ yield this.enableAndroidHighConnectionPriority(uuid);
1574
1946
  }
1575
1947
  try {
1576
- const session = new transport.ProtocolV2Session({
1577
- schemas: {
1578
- protocolV1: this._messages,
1579
- protocolV2: this._messagesV2,
1580
- },
1581
- router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1582
- writeFrame: (frame) => __awaiter(this, void 0, void 0, function* () {
1583
- yield this.writeProtocolV2Frame(transport$1, frame, {
1584
- highVolume: highVolumeWrite,
1585
- });
1586
- }),
1587
- readFrame: () => __awaiter(this, void 0, void 0, function* () {
1588
- const rxFrame = yield this.readProtocolV2Frame(uuid);
1589
- if (!(rxFrame instanceof Uint8Array)) {
1590
- throw new Error('Protocol V2 response is not Uint8Array');
1591
- }
1592
- return rxFrame;
1593
- }),
1594
- logger: Log,
1595
- logPrefix: 'ProtocolV2 RN-BLE',
1596
- createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
1597
- });
1598
- const result = yield session.call(name, data, callOptions);
1599
- completed = true;
1600
- return result;
1948
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1601
1949
  }
1602
1950
  catch (e) {
1603
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1604
- (_c = this.protocolV2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
1605
- this.resetProtocolV2Frames(uuid);
1606
- }
1607
1951
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1608
1952
  throw e;
1609
1953
  }
1610
1954
  finally {
1611
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1612
- if (!completed) {
1613
- (_d = this.protocolV2Assemblers.get(uuid)) === null || _d === void 0 ? void 0 : _d.reset();
1614
- }
1615
- this.resetProtocolV2Frames(uuid);
1616
- this.activeProtocolV2Call = null;
1617
- }
1618
- if (this.runPromise === runPromise) {
1619
- this.runPromise = null;
1955
+ if (highVolumeWrite) {
1956
+ this.scheduleAndroidBalancedConnectionPriority(uuid);
1620
1957
  }
1621
1958
  }
1622
1959
  });
1623
1960
  }
1961
+ clearAndroidPriorityResetTimer(uuid) {
1962
+ const timerId = this.androidPriorityResetTimers.get(uuid);
1963
+ if (timerId !== undefined) {
1964
+ clearTimeout(timerId);
1965
+ this.androidPriorityResetTimers.delete(uuid);
1966
+ }
1967
+ }
1968
+ enableAndroidHighConnectionPriority(uuid) {
1969
+ return __awaiter(this, void 0, void 0, function* () {
1970
+ if (reactNative.Platform.OS !== 'android')
1971
+ return;
1972
+ this.clearAndroidPriorityResetTimer(uuid);
1973
+ if (this.androidHighPriorityDevices.has(uuid))
1974
+ return;
1975
+ const transport = transportCache[uuid];
1976
+ if (!transport)
1977
+ return;
1978
+ try {
1979
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.High);
1980
+ this.androidHighPriorityDevices.add(uuid);
1981
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
1982
+ priority: 'high',
1983
+ });
1984
+ }
1985
+ catch (error) {
1986
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
1987
+ error: error instanceof Error ? error.message : String(error),
1988
+ });
1989
+ }
1990
+ });
1991
+ }
1992
+ scheduleAndroidBalancedConnectionPriority(uuid) {
1993
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid))
1994
+ return;
1995
+ this.clearAndroidPriorityResetTimer(uuid);
1996
+ const timerId = setTimeout(() => {
1997
+ this.androidPriorityResetTimers.delete(uuid);
1998
+ this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error => Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error));
1999
+ }, ANDROID_HIGH_PRIORITY_IDLE_MS);
2000
+ this.androidPriorityResetTimers.set(uuid, timerId);
2001
+ }
2002
+ restoreAndroidConnectionPriority(uuid, transport) {
2003
+ return __awaiter(this, void 0, void 0, function* () {
2004
+ this.clearAndroidPriorityResetTimer(uuid);
2005
+ if (reactNative.Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
2006
+ return;
2007
+ }
2008
+ try {
2009
+ transport.device = yield transport.device.requestConnectionPriority(reactNativeBlePlx.ConnectionPriority.Balanced);
2010
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
2011
+ priority: 'balanced',
2012
+ });
2013
+ }
2014
+ catch (error) {
2015
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
2016
+ error: error instanceof Error ? error.message : String(error),
2017
+ });
2018
+ }
2019
+ });
2020
+ }
2021
+ createProtocolV2Adapter(uuid) {
2022
+ var _a;
2023
+ const generation = (_a = this.monitorTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
2024
+ const assertCurrentGeneration = () => {
2025
+ if (this.monitorTokens.get(uuid) !== generation) {
2026
+ throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
2027
+ }
2028
+ };
2029
+ return {
2030
+ router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
2031
+ maxFrameBytes: transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
2032
+ generation,
2033
+ prepareCall: () => {
2034
+ var _a;
2035
+ assertCurrentGeneration();
2036
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2037
+ this.resetProtocolV2Frames(uuid);
2038
+ },
2039
+ writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
2040
+ assertCurrentGeneration();
2041
+ const currentTransport = this.getCachedTransport(uuid);
2042
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
2043
+ }),
2044
+ readFrame: () => __awaiter(this, void 0, void 0, function* () {
2045
+ assertCurrentGeneration();
2046
+ const rxFrame = yield this.readProtocolV2Frame(uuid);
2047
+ if (!(rxFrame instanceof Uint8Array)) {
2048
+ throw new Error('Protocol V2 response is not Uint8Array');
2049
+ }
2050
+ return rxFrame;
2051
+ }),
2052
+ reset: (reason) => {
2053
+ var _a;
2054
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2055
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
2056
+ },
2057
+ logger: Log,
2058
+ logPrefix: 'ProtocolV2 RN-BLE',
2059
+ createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
2060
+ };
2061
+ }
1624
2062
  getProtocolType(path) {
1625
- return this.deviceProtocol.get(path);
2063
+ return this.getActiveProtocol(path);
1626
2064
  }
1627
2065
  }
1628
2066
 
2067
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2068
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2069
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2070
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2071
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2072
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1629
2073
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1630
2074
  exports["default"] = ReactNativeBleTransport;
2075
+ exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
1631
2076
  exports.getProtocolV2BleTuning = getProtocolV2BleTuning;
1632
2077
  exports.resetProtocolV2BleTuning = resetProtocolV2BleTuning;