@onekeyfe/hd-transport-react-native 1.2.0-alpha.69 → 1.2.0-alpha.70
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/BleTransport.d.ts +1 -1
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +0 -6
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +80 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +341 -210
- package/jest.config.js +5 -0
- package/package.json +5 -5
- package/src/BleTransport.ts +1 -1
- package/src/__tests__/bleStrategy.test.ts +7 -77
- package/src/__tests__/connectTimeout.test.ts +281 -0
- package/src/__tests__/protocolReprobe.test.ts +132 -0
- package/src/__tests__/protocolV1SchemaFixture.ts +39 -0
- package/src/__tests__/protocolV2Link.test.ts +28 -121
- package/src/__tests__/staleCallTimeout.test.ts +210 -0
- package/src/__tests__/writePacketTimeout.test.ts +305 -0
- package/src/bleStrategy.ts +10 -23
- package/src/constants.ts +1 -2
- package/src/index.ts +486 -229
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
BleError,
|
|
6
6
|
BleErrorCode,
|
|
7
7
|
BleManager as BlePlxManager,
|
|
8
|
-
ConnectionPriority,
|
|
9
8
|
ScanMode,
|
|
10
9
|
} from 'react-native-ble-plx';
|
|
11
10
|
import ByteBuffer from 'bytebuffer';
|
|
@@ -33,17 +32,11 @@ import {
|
|
|
33
32
|
} from '@onekeyfe/hd-shared';
|
|
34
33
|
|
|
35
34
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
36
|
-
import {
|
|
37
|
-
hasWritableCapability,
|
|
38
|
-
resolveProtocolV2PacketCapacity,
|
|
39
|
-
shouldWriteProtocolV2WithResponse,
|
|
40
|
-
} from './bleStrategy';
|
|
35
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
41
36
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
42
37
|
import {
|
|
43
38
|
ANDROID_PACKET_LENGTH,
|
|
44
|
-
ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
45
39
|
IOS_PACKET_LENGTH,
|
|
46
|
-
IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
47
40
|
getBluetoothServiceUuids,
|
|
48
41
|
getInfosForServiceUuid,
|
|
49
42
|
isSameBleUuid,
|
|
@@ -68,6 +61,7 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
68
61
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
69
62
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
70
63
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
64
|
+
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
71
65
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
72
66
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
73
67
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
@@ -141,6 +135,22 @@ const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, max
|
|
|
141
135
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
142
136
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
143
137
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
138
|
+
/**
|
|
139
|
+
* Per-packet write budget. iOS only resolves writeWithoutResponse once CoreBluetooth
|
|
140
|
+
* reports the peripheral ready again; a peripheral wedged by its own firmware reboot
|
|
141
|
+
* stops reporting ready while staying connected, so the write promise never settles.
|
|
142
|
+
* Response timeouts cannot cover that — they are armed after the writes complete —
|
|
143
|
+
* and an unbounded write leaves the whole transport unusable until the process dies.
|
|
144
|
+
* A healthy packet completes in milliseconds, so this only fires on a dead link.
|
|
145
|
+
*/
|
|
146
|
+
export const BLE_WRITE_PACKET_TIMEOUT_MS = 10_000;
|
|
147
|
+
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
148
|
+
const isWedgedWriteError = (error: unknown): boolean =>
|
|
149
|
+
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleWriteCharacteristicError &&
|
|
150
|
+
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
151
|
+
(error as { message: string }).message.startsWith(WEDGED_WRITE_MESSAGE);
|
|
152
|
+
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
153
|
+
export const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
144
154
|
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
145
155
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
146
156
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
@@ -152,8 +162,8 @@ export type ProtocolV2BleTuning = {
|
|
|
152
162
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
153
163
|
|
|
154
164
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
155
|
-
iosPacketLength:
|
|
156
|
-
androidPacketLength:
|
|
165
|
+
iosPacketLength: IOS_PACKET_LENGTH,
|
|
166
|
+
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
157
167
|
};
|
|
158
168
|
|
|
159
169
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -195,21 +205,54 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
195
205
|
return device?.name || device?.localName || null;
|
|
196
206
|
}
|
|
197
207
|
|
|
198
|
-
const
|
|
199
|
-
const ANDROID_REQUEST_MTU = 517;
|
|
200
|
-
const BLE_MTU_REFRESH_THRESHOLD = 247;
|
|
201
|
-
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
202
|
-
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
208
|
+
const ANDROID_REQUEST_MTU = 256;
|
|
203
209
|
|
|
204
|
-
const
|
|
205
|
-
Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
210
|
+
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
206
211
|
|
|
207
212
|
const connectOptions: Record<string, unknown> = {
|
|
208
|
-
requestMTU:
|
|
209
|
-
timeout:
|
|
213
|
+
requestMTU: ANDROID_REQUEST_MTU,
|
|
214
|
+
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
210
215
|
refreshGatt: 'OnConnected',
|
|
211
216
|
};
|
|
212
217
|
|
|
218
|
+
/** Fallback connect options: drops requestMTU (the thing being worked around) but keeps the native budget. */
|
|
219
|
+
const fallbackConnectOptions: Record<string, unknown> = {
|
|
220
|
+
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* JS backstop for connect. The native adapter applies its own 3s budget, but it
|
|
225
|
+
* schedules that timeout on its serial queue, so a busy queue (e.g. right after a
|
|
226
|
+
* firmware install tears the link down) can leave the promise unsettled — observed
|
|
227
|
+
* blocking a reconnect for 61s until the app-level timeout. Healthy connects finish
|
|
228
|
+
* inside the native budget, so this only fires when the native timeout did not.
|
|
229
|
+
*/
|
|
230
|
+
export const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
231
|
+
/**
|
|
232
|
+
* Service discovery and characteristic resolution run after connect() succeeds, but
|
|
233
|
+
* CoreBluetooth schedules them on the same serial queue. If that queue is wedged by a
|
|
234
|
+
* device reboot, these calls can remain pending forever unless they have their own
|
|
235
|
+
* budget.
|
|
236
|
+
*/
|
|
237
|
+
export const BLE_GATT_SETUP_TIMEOUT_MS = 10_000;
|
|
238
|
+
/**
|
|
239
|
+
* How many times a known device may fail its own protocol before we probe the others
|
|
240
|
+
* again. Reconnect polling during a device reboot repeats this every few seconds, and
|
|
241
|
+
* probing Protocol V2 costs a 10s Ping timeout, so paying it on every attempt for a
|
|
242
|
+
* device we just spoke V1 to dominates the wait. A firmware update can legitimately
|
|
243
|
+
* change a device's protocol, so the shortcut has to expire rather than stick.
|
|
244
|
+
*/
|
|
245
|
+
export const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
246
|
+
/** BLE setup timeouts since the last successful setup before the manager is recreated. */
|
|
247
|
+
export const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
248
|
+
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
249
|
+
const isConnectTimeoutError = (error: unknown): boolean =>
|
|
250
|
+
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleConnectedError &&
|
|
251
|
+
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
252
|
+
(error as { message: string }).message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
253
|
+
const isNativeOperationTimeoutError = (error: unknown): boolean =>
|
|
254
|
+
(error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
|
|
255
|
+
|
|
213
256
|
export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
214
257
|
|
|
215
258
|
const tryToGetConfiguration = (device: Device) => {
|
|
@@ -221,32 +264,23 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
221
264
|
return infos;
|
|
222
265
|
};
|
|
223
266
|
|
|
224
|
-
const
|
|
225
|
-
device
|
|
226
|
-
stage: 'connected' | 'servicesAndNotifyReady',
|
|
227
|
-
attempt: number
|
|
228
|
-
) => {
|
|
229
|
-
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
267
|
+
const requestAndroidMtu = async (device: Device) => {
|
|
268
|
+
if (Platform.OS !== 'android') return device;
|
|
230
269
|
|
|
231
270
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
271
|
+
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
272
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
273
|
+
deviceId: device.id,
|
|
274
|
+
requested: ANDROID_REQUEST_MTU,
|
|
275
|
+
actual: mtuDevice.mtu,
|
|
276
|
+
});
|
|
235
277
|
return mtuDevice;
|
|
236
278
|
} catch (error) {
|
|
237
|
-
Log?.debug('[ReactNativeBleTransport] MTU
|
|
238
|
-
platform: Platform.OS,
|
|
239
|
-
stage,
|
|
240
|
-
attempt,
|
|
241
|
-
actual: device.mtu,
|
|
242
|
-
error: error instanceof Error ? error.message : String(error),
|
|
243
|
-
});
|
|
279
|
+
Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
|
|
244
280
|
return device;
|
|
245
281
|
}
|
|
246
282
|
};
|
|
247
283
|
|
|
248
|
-
const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
249
|
-
|
|
250
284
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
251
285
|
|
|
252
286
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -307,8 +341,28 @@ export default class ReactNativeBleTransport {
|
|
|
307
341
|
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
308
342
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
309
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Protocol a probe is currently trying, before the device has confirmed it. Calls
|
|
346
|
+
* must route with it, but acquire() must not treat it as a detected protocol: a
|
|
347
|
+
* probe that never answers would otherwise leave the reuse fast path handing out a
|
|
348
|
+
* transport that was never validated.
|
|
349
|
+
*/
|
|
350
|
+
private probingProtocols: Map<string, ProtocolType> = new Map();
|
|
351
|
+
|
|
352
|
+
/** Consecutive write timeouts per device; reset by any write that completes. */
|
|
353
|
+
private writeTimeoutCounts: Map<string, number> = new Map();
|
|
354
|
+
|
|
355
|
+
/** BLE setup timeouts per device since the last complete characteristic resolution. */
|
|
356
|
+
private connectionSetupTimeoutCounts: Map<string, number> = new Map();
|
|
357
|
+
|
|
310
358
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
311
359
|
|
|
360
|
+
/** Protocol this device actually answered on, kept across reconnects of one session. */
|
|
361
|
+
private sessionProtocols: Map<string, ProtocolType> = new Map();
|
|
362
|
+
|
|
363
|
+
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
364
|
+
private protocolReprobeFailures: Map<string, number> = new Map();
|
|
365
|
+
|
|
312
366
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
313
367
|
|
|
314
368
|
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
@@ -340,12 +394,6 @@ export default class ReactNativeBleTransport {
|
|
|
340
394
|
|
|
341
395
|
private disconnectEventTokens: Map<string, number> = new Map();
|
|
342
396
|
|
|
343
|
-
private protocolV2HighVolumeLogSignatures: Map<string, Set<string>> = new Map();
|
|
344
|
-
|
|
345
|
-
private androidHighPriorityDevices: Set<string> = new Set();
|
|
346
|
-
|
|
347
|
-
private androidPriorityResetTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
348
|
-
|
|
349
397
|
private nextMonitorToken = 1;
|
|
350
398
|
|
|
351
399
|
constructor(options: TransportOptions) {
|
|
@@ -532,22 +580,21 @@ export default class ReactNativeBleTransport {
|
|
|
532
580
|
const isConnected = await device.isConnected().catch(() => false);
|
|
533
581
|
if (!isConnected) {
|
|
534
582
|
try {
|
|
535
|
-
device = await device.connect(connectOptions);
|
|
583
|
+
device = await this.connectWithTimeout(uuid, () => device.connect(connectOptions));
|
|
536
584
|
} catch (e) {
|
|
537
585
|
if (
|
|
538
586
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
539
587
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
540
588
|
) {
|
|
541
|
-
device = await device.connect();
|
|
589
|
+
device = await this.connectWithTimeout(uuid, () => device.connect());
|
|
542
590
|
} else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
|
|
543
591
|
throw e;
|
|
544
592
|
}
|
|
545
593
|
}
|
|
546
594
|
}
|
|
547
595
|
|
|
548
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
549
|
-
device
|
|
550
|
-
);
|
|
596
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
597
|
+
await this.resolveCharacteristicsWithTimeout(uuid, device);
|
|
551
598
|
|
|
552
599
|
transport.device = device;
|
|
553
600
|
transport.writeCharacteristic = writeCharacteristic;
|
|
@@ -726,9 +773,11 @@ export default class ReactNativeBleTransport {
|
|
|
726
773
|
characteristics?: ResolvedBleCharacteristics
|
|
727
774
|
) {
|
|
728
775
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
729
|
-
characteristics ?? (await this.
|
|
776
|
+
characteristics ?? (await this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
730
777
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
731
|
-
|
|
778
|
+
if (Platform.OS === 'android') {
|
|
779
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
780
|
+
}
|
|
732
781
|
const monitorToken = this.nextMonitorToken;
|
|
733
782
|
this.nextMonitorToken += 1;
|
|
734
783
|
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
@@ -742,7 +791,6 @@ export default class ReactNativeBleTransport {
|
|
|
742
791
|
notifyTransactionId
|
|
743
792
|
);
|
|
744
793
|
transportCache[uuid] = transport;
|
|
745
|
-
this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
|
|
746
794
|
this.protocolV2Assemblers.set(
|
|
747
795
|
uuid,
|
|
748
796
|
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
@@ -756,40 +804,6 @@ export default class ReactNativeBleTransport {
|
|
|
756
804
|
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
757
805
|
}
|
|
758
806
|
|
|
759
|
-
const initialMtu = transport.mtuSize;
|
|
760
|
-
let refreshAttempts = 0;
|
|
761
|
-
if (
|
|
762
|
-
(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
763
|
-
(typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD)
|
|
764
|
-
) {
|
|
765
|
-
refreshAttempts += 1;
|
|
766
|
-
let refreshedDevice = await requestNegotiatedMtu(
|
|
767
|
-
transport.device,
|
|
768
|
-
'servicesAndNotifyReady',
|
|
769
|
-
1
|
|
770
|
-
);
|
|
771
|
-
transport.device = refreshedDevice;
|
|
772
|
-
transport.mtuSize =
|
|
773
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
774
|
-
|
|
775
|
-
if (typeof transport.mtuSize !== 'number' || transport.mtuSize < BLE_MTU_REFRESH_THRESHOLD) {
|
|
776
|
-
await delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
777
|
-
refreshAttempts += 1;
|
|
778
|
-
refreshedDevice = await requestNegotiatedMtu(transport.device, 'servicesAndNotifyReady', 2);
|
|
779
|
-
transport.device = refreshedDevice;
|
|
780
|
-
transport.mtuSize =
|
|
781
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
Log?.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
786
|
-
platform: Platform.OS,
|
|
787
|
-
requested: getRequestedBleMtu(),
|
|
788
|
-
initial: initialMtu,
|
|
789
|
-
actual: transport.mtuSize,
|
|
790
|
-
refreshAttempts,
|
|
791
|
-
});
|
|
792
|
-
|
|
793
807
|
return transport;
|
|
794
808
|
}
|
|
795
809
|
|
|
@@ -863,15 +877,22 @@ export default class ReactNativeBleTransport {
|
|
|
863
877
|
if (!device) {
|
|
864
878
|
Log?.debug('try to connect to device: ', uuid);
|
|
865
879
|
try {
|
|
866
|
-
device = await
|
|
880
|
+
device = await this.connectWithTimeout(uuid, () =>
|
|
881
|
+
blePlxManager.connectToDevice(uuid, connectOptions)
|
|
882
|
+
);
|
|
867
883
|
} catch (e) {
|
|
868
884
|
Log?.debug('try to connect to device has error: ', e);
|
|
885
|
+
if (isConnectTimeoutError(e)) {
|
|
886
|
+
throw e;
|
|
887
|
+
}
|
|
869
888
|
if (
|
|
870
889
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
871
890
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
872
891
|
) {
|
|
873
892
|
Log?.debug('first try to reconnect without params');
|
|
874
|
-
device = await
|
|
893
|
+
device = await this.connectWithTimeout(uuid, () =>
|
|
894
|
+
blePlxManager.connectToDevice(uuid, fallbackConnectOptions)
|
|
895
|
+
);
|
|
875
896
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
876
897
|
Log?.debug('device already connected');
|
|
877
898
|
throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
|
|
@@ -887,26 +908,36 @@ export default class ReactNativeBleTransport {
|
|
|
887
908
|
|
|
888
909
|
if (!(await device.isConnected())) {
|
|
889
910
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
911
|
+
const disconnectedDevice = device;
|
|
890
912
|
|
|
891
913
|
try {
|
|
892
|
-
device = await
|
|
914
|
+
device = await this.connectWithTimeout(uuid, () =>
|
|
915
|
+
disconnectedDevice.connect(connectOptions)
|
|
916
|
+
);
|
|
893
917
|
} catch (e) {
|
|
894
918
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
919
|
+
if (isConnectTimeoutError(e)) {
|
|
920
|
+
throw e;
|
|
921
|
+
}
|
|
895
922
|
if (
|
|
896
923
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
897
924
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
898
925
|
) {
|
|
899
926
|
Log?.debug('second try to reconnect without params');
|
|
900
927
|
try {
|
|
901
|
-
device = await
|
|
928
|
+
device = await this.connectWithTimeout(uuid, () =>
|
|
929
|
+
disconnectedDevice.connect(fallbackConnectOptions)
|
|
930
|
+
);
|
|
902
931
|
} catch (e) {
|
|
903
932
|
Log?.debug('last try to reconnect error: ', e);
|
|
904
933
|
// last try to reconnect device if this issue exists
|
|
905
934
|
// https://github.com/dotintent/react-native-ble-plx/issues/426
|
|
906
935
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
907
936
|
Log?.debug('last try to reconnect');
|
|
908
|
-
await
|
|
909
|
-
device = await
|
|
937
|
+
await disconnectedDevice.cancelConnection();
|
|
938
|
+
device = await this.connectWithTimeout(uuid, () =>
|
|
939
|
+
disconnectedDevice.connect(fallbackConnectOptions)
|
|
940
|
+
);
|
|
910
941
|
}
|
|
911
942
|
}
|
|
912
943
|
} else {
|
|
@@ -915,11 +946,10 @@ export default class ReactNativeBleTransport {
|
|
|
915
946
|
}
|
|
916
947
|
}
|
|
917
948
|
|
|
918
|
-
device = await
|
|
949
|
+
device = await requestAndroidMtu(device);
|
|
919
950
|
const acquiredDevice = device;
|
|
920
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
921
|
-
acquiredDevice
|
|
922
|
-
);
|
|
951
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
952
|
+
await this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
923
953
|
|
|
924
954
|
const protocolHint = expectedProtocol
|
|
925
955
|
? undefined
|
|
@@ -951,7 +981,7 @@ export default class ReactNativeBleTransport {
|
|
|
951
981
|
if (!currentTransport) {
|
|
952
982
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
953
983
|
}
|
|
954
|
-
this.attachDisconnectSubscription(currentTransport,
|
|
984
|
+
this.attachDisconnectSubscription(currentTransport, acquiredDevice, uuid);
|
|
955
985
|
return { uuid, protocolType };
|
|
956
986
|
} catch (error) {
|
|
957
987
|
await this.release(uuid, true);
|
|
@@ -983,7 +1013,7 @@ export default class ReactNativeBleTransport {
|
|
|
983
1013
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
984
1014
|
return;
|
|
985
1015
|
}
|
|
986
|
-
if (this.
|
|
1016
|
+
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
987
1017
|
let errorCode:
|
|
988
1018
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
989
1019
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -1053,7 +1083,7 @@ export default class ReactNativeBleTransport {
|
|
|
1053
1083
|
|
|
1054
1084
|
try {
|
|
1055
1085
|
const data = Buffer.from(c.value as string, 'base64');
|
|
1056
|
-
const protocol = this.
|
|
1086
|
+
const protocol = this.getActiveProtocol(uuid);
|
|
1057
1087
|
if (!protocol) {
|
|
1058
1088
|
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
1059
1089
|
return;
|
|
@@ -1087,7 +1117,7 @@ export default class ReactNativeBleTransport {
|
|
|
1087
1117
|
} catch (error) {
|
|
1088
1118
|
Log?.debug('monitor data error: ', error);
|
|
1089
1119
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1090
|
-
if (this.
|
|
1120
|
+
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
1091
1121
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1092
1122
|
} else if (this.runPromiseDeviceId === uuid) {
|
|
1093
1123
|
this.runPromise?.reject(notifyError);
|
|
@@ -1121,8 +1151,6 @@ export default class ReactNativeBleTransport {
|
|
|
1121
1151
|
return Promise.resolve(true);
|
|
1122
1152
|
}
|
|
1123
1153
|
|
|
1124
|
-
await this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1125
|
-
|
|
1126
1154
|
if (transport) {
|
|
1127
1155
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1128
1156
|
this.monitorTokens.delete(uuid);
|
|
@@ -1152,9 +1180,8 @@ export default class ReactNativeBleTransport {
|
|
|
1152
1180
|
delete transportCache[uuid];
|
|
1153
1181
|
}
|
|
1154
1182
|
|
|
1155
|
-
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1156
|
-
|
|
1157
1183
|
this.deviceProtocol.delete(uuid);
|
|
1184
|
+
this.probingProtocols.delete(uuid);
|
|
1158
1185
|
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
1159
1186
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1160
1187
|
this.protocolV2Assemblers.delete(uuid);
|
|
@@ -1221,8 +1248,25 @@ export default class ReactNativeBleTransport {
|
|
|
1221
1248
|
const transport = this.getCachedTransport(uuid);
|
|
1222
1249
|
const runPromise = createDeferred<string>();
|
|
1223
1250
|
runPromise.promise.catch(() => undefined);
|
|
1251
|
+
const supersededRunPromise = this.runPromise;
|
|
1252
|
+
if (supersededRunPromise) {
|
|
1253
|
+
// Only forceRun calls (Initialize/Cancel) reach here with a pending call. Settle
|
|
1254
|
+
// the superseded deferred now so its response race resolves and its finally block
|
|
1255
|
+
// clears its timeout timer; an orphaned timer would otherwise fire much later and
|
|
1256
|
+
// tear down the shared connection while another call is using it.
|
|
1257
|
+
supersededRunPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
1258
|
+
}
|
|
1224
1259
|
this.runPromise = runPromise;
|
|
1225
1260
|
this.runPromiseDeviceId = uuid;
|
|
1261
|
+
// A superseded call's late write failure must not clear the successor's ownership;
|
|
1262
|
+
// only the call that still owns the slot may release it.
|
|
1263
|
+
const releaseOwnershipIfCurrent = () => {
|
|
1264
|
+
if (this.runPromise === runPromise) {
|
|
1265
|
+
this.runPromise = null;
|
|
1266
|
+
this.runPromiseDeviceId = null;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const isCurrentOwner = () => this.runPromise === runPromise;
|
|
1226
1270
|
const messages = this._messages;
|
|
1227
1271
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1228
1272
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1248,6 +1292,9 @@ export default class ReactNativeBleTransport {
|
|
|
1248
1292
|
chunk = ByteBuffer.allocate(packetCapacity);
|
|
1249
1293
|
} catch (e) {
|
|
1250
1294
|
onError(e);
|
|
1295
|
+
if (isWedgedWriteError(e)) {
|
|
1296
|
+
throw e;
|
|
1297
|
+
}
|
|
1251
1298
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1252
1299
|
}
|
|
1253
1300
|
}
|
|
@@ -1279,6 +1326,9 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1326
|
}
|
|
1280
1327
|
} catch (e) {
|
|
1281
1328
|
onError(e);
|
|
1329
|
+
if (isWedgedWriteError(e)) {
|
|
1330
|
+
throw e;
|
|
1331
|
+
}
|
|
1282
1332
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1283
1333
|
}
|
|
1284
1334
|
}
|
|
@@ -1292,9 +1342,15 @@ export default class ReactNativeBleTransport {
|
|
|
1292
1342
|
if (name === 'EmmcFileWrite') {
|
|
1293
1343
|
await writeChunkedData(
|
|
1294
1344
|
buffers,
|
|
1295
|
-
data =>
|
|
1345
|
+
data =>
|
|
1346
|
+
this.writeBlePacket(
|
|
1347
|
+
uuid,
|
|
1348
|
+
data,
|
|
1349
|
+
payload => transport.writeWithRetry(payload),
|
|
1350
|
+
isCurrentOwner
|
|
1351
|
+
),
|
|
1296
1352
|
e => {
|
|
1297
|
-
|
|
1353
|
+
releaseOwnershipIfCurrent();
|
|
1298
1354
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1299
1355
|
}
|
|
1300
1356
|
);
|
|
@@ -1315,7 +1371,12 @@ export default class ReactNativeBleTransport {
|
|
|
1315
1371
|
// eslint-disable-next-line no-constant-condition
|
|
1316
1372
|
while (true) {
|
|
1317
1373
|
try {
|
|
1318
|
-
await
|
|
1374
|
+
await this.writeBlePacket(
|
|
1375
|
+
uuid,
|
|
1376
|
+
data,
|
|
1377
|
+
payload => transport.writeWithRetry(payload),
|
|
1378
|
+
isCurrentOwner
|
|
1379
|
+
);
|
|
1319
1380
|
return;
|
|
1320
1381
|
} catch (error) {
|
|
1321
1382
|
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
@@ -1334,7 +1395,7 @@ export default class ReactNativeBleTransport {
|
|
|
1334
1395
|
}
|
|
1335
1396
|
},
|
|
1336
1397
|
e => {
|
|
1337
|
-
|
|
1398
|
+
releaseOwnershipIfCurrent();
|
|
1338
1399
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1339
1400
|
}
|
|
1340
1401
|
);
|
|
@@ -1345,14 +1406,21 @@ export default class ReactNativeBleTransport {
|
|
|
1345
1406
|
try {
|
|
1346
1407
|
const shouldUseWriteWithResponse =
|
|
1347
1408
|
Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1409
|
+
await this.writeBlePacket(
|
|
1410
|
+
uuid,
|
|
1411
|
+
outData,
|
|
1412
|
+
payload =>
|
|
1413
|
+
shouldUseWriteWithResponse
|
|
1414
|
+
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
1415
|
+
: transport.writeCharacteristic.writeWithoutResponse(payload),
|
|
1416
|
+
isCurrentOwner
|
|
1417
|
+
);
|
|
1353
1418
|
} catch (e) {
|
|
1354
1419
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1355
|
-
|
|
1420
|
+
releaseOwnershipIfCurrent();
|
|
1421
|
+
if (isWedgedWriteError(e)) {
|
|
1422
|
+
throw e;
|
|
1423
|
+
}
|
|
1356
1424
|
if (e.errorCode === BleErrorCode.DeviceDisconnected) {
|
|
1357
1425
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
|
|
1358
1426
|
} else if (e.errorCode === BleErrorCode.OperationStartFailed) {
|
|
@@ -1395,8 +1463,13 @@ export default class ReactNativeBleTransport {
|
|
|
1395
1463
|
}
|
|
1396
1464
|
const isProbeTimeout =
|
|
1397
1465
|
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1466
|
+
// A call that has been superseded (forceRun) or cleaned up no longer owns the
|
|
1467
|
+
// transport; its late timeout must not tear down the connection the current
|
|
1468
|
+
// call is actively using.
|
|
1469
|
+
const isStaleCall = this.runPromise !== runPromise;
|
|
1398
1470
|
if (
|
|
1399
1471
|
!isProbeTimeout &&
|
|
1472
|
+
!isStaleCall &&
|
|
1400
1473
|
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1401
1474
|
) {
|
|
1402
1475
|
await this.disconnect(uuid);
|
|
@@ -1476,7 +1549,10 @@ export default class ReactNativeBleTransport {
|
|
|
1476
1549
|
delete transportCache[session];
|
|
1477
1550
|
}
|
|
1478
1551
|
this.deviceProtocol.delete(session);
|
|
1552
|
+
this.probingProtocols.delete(session);
|
|
1479
1553
|
this.deviceProtocolHints.delete(session);
|
|
1554
|
+
this.sessionProtocols.delete(session);
|
|
1555
|
+
this.protocolReprobeFailures.delete(session);
|
|
1480
1556
|
this.protocolV2Assemblers.delete(session);
|
|
1481
1557
|
this.resetProtocolV2Frames(session);
|
|
1482
1558
|
|
|
@@ -1502,6 +1578,113 @@ export default class ReactNativeBleTransport {
|
|
|
1502
1578
|
this.runPromiseDeviceId = null;
|
|
1503
1579
|
}
|
|
1504
1580
|
|
|
1581
|
+
/** Run a native connect under the JS backstop budget. */
|
|
1582
|
+
private async connectWithTimeout<T>(uuid: string, connect: () => Promise<T>): Promise<T> {
|
|
1583
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1584
|
+
let timedOut = false;
|
|
1585
|
+
const pending = connect();
|
|
1586
|
+
// The abandoned attempt keeps running; swallow its late outcome so it cannot
|
|
1587
|
+
// surface as an unhandled rejection after we have already given up on it.
|
|
1588
|
+
pending.catch(() => undefined);
|
|
1589
|
+
try {
|
|
1590
|
+
const result = await Promise.race([
|
|
1591
|
+
pending,
|
|
1592
|
+
new Promise<never>((_, reject) => {
|
|
1593
|
+
timer = setTimeout(() => {
|
|
1594
|
+
timedOut = true;
|
|
1595
|
+
reject(
|
|
1596
|
+
ERRORS.TypedError(
|
|
1597
|
+
HardwareErrorCode.BleConnectedError,
|
|
1598
|
+
`BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`
|
|
1599
|
+
)
|
|
1600
|
+
);
|
|
1601
|
+
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1602
|
+
}),
|
|
1603
|
+
]);
|
|
1604
|
+
return result;
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1607
|
+
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1608
|
+
}
|
|
1609
|
+
throw error;
|
|
1610
|
+
} finally {
|
|
1611
|
+
if (timer) clearTimeout(timer);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/** Resolve the complete GATT shape under a budget so acquire() always settles. */
|
|
1616
|
+
private async resolveCharacteristicsWithTimeout(
|
|
1617
|
+
uuid: string,
|
|
1618
|
+
device: Device
|
|
1619
|
+
): Promise<ResolvedBleCharacteristics> {
|
|
1620
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1621
|
+
let timedOut = false;
|
|
1622
|
+
const pending = this.resolveCharacteristics(device);
|
|
1623
|
+
pending.catch(() => undefined);
|
|
1624
|
+
try {
|
|
1625
|
+
const result = await Promise.race([
|
|
1626
|
+
pending,
|
|
1627
|
+
new Promise<never>((_, reject) => {
|
|
1628
|
+
timer = setTimeout(() => {
|
|
1629
|
+
timedOut = true;
|
|
1630
|
+
reject(
|
|
1631
|
+
ERRORS.TypedError(
|
|
1632
|
+
HardwareErrorCode.BleConnectedError,
|
|
1633
|
+
`BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`
|
|
1634
|
+
)
|
|
1635
|
+
);
|
|
1636
|
+
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1637
|
+
}),
|
|
1638
|
+
]);
|
|
1639
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1640
|
+
return result;
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1643
|
+
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1644
|
+
}
|
|
1645
|
+
throw error;
|
|
1646
|
+
} finally {
|
|
1647
|
+
if (timer) clearTimeout(timer);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
/**
|
|
1652
|
+
* Give up on a BLE setup operation the native layer did not settle. The abandoned
|
|
1653
|
+
* operation still owns native connection/GATT state that can poison the next attempt,
|
|
1654
|
+
* so it is cleared here without awaiting the same queue that stopped responding.
|
|
1655
|
+
*/
|
|
1656
|
+
private abandonStalledConnection(
|
|
1657
|
+
uuid: string,
|
|
1658
|
+
stage: 'connect-backstop' | 'connect-native' | 'gatt-backstop' | 'gatt-native'
|
|
1659
|
+
) {
|
|
1660
|
+
const timeouts = (this.connectionSetupTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1661
|
+
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1662
|
+
Log?.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1663
|
+
stage,
|
|
1664
|
+
setupTimeoutsSinceSuccess: timeouts,
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
this.blePlxManager?.cancelDeviceConnection(uuid).catch(() => {
|
|
1668
|
+
// Rejects with "Operation was cancelled" while merely connecting — expected.
|
|
1669
|
+
});
|
|
1670
|
+
const stalled = transportCache[uuid];
|
|
1671
|
+
if (stalled) {
|
|
1672
|
+
delete transportCache[uuid];
|
|
1673
|
+
}
|
|
1674
|
+
this.deviceProtocol.delete(uuid);
|
|
1675
|
+
this.probingProtocols.delete(uuid);
|
|
1676
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
1677
|
+
this.resetProtocolV2Frames(uuid);
|
|
1678
|
+
|
|
1679
|
+
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1680
|
+
// BleManager.destroy() force-rejects every promise the native queue abandoned —
|
|
1681
|
+
// the only JS-reachable way to settle them — and drops all cached peripherals.
|
|
1682
|
+
Log?.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1683
|
+
this.resetPlxManager();
|
|
1684
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1505
1688
|
private getCachedTransport(uuid: string) {
|
|
1506
1689
|
const transport = transportCache[uuid];
|
|
1507
1690
|
if (!transport) {
|
|
@@ -1510,6 +1693,107 @@ export default class ReactNativeBleTransport {
|
|
|
1510
1693
|
return transport;
|
|
1511
1694
|
}
|
|
1512
1695
|
|
|
1696
|
+
/**
|
|
1697
|
+
* Write one packet under a bounded budget. A write that never settles means the
|
|
1698
|
+
* peripheral is wedged even though the GATT link still reports connected, so the
|
|
1699
|
+
* link is torn down: releasing JS state alone would leave the poisoned peripheral
|
|
1700
|
+
* cached and every later call would hang on it again.
|
|
1701
|
+
*/
|
|
1702
|
+
private async writeBlePacket(
|
|
1703
|
+
uuid: string,
|
|
1704
|
+
data: string,
|
|
1705
|
+
write: (payload: string) => Promise<unknown>,
|
|
1706
|
+
isCurrentOwner?: () => boolean
|
|
1707
|
+
) {
|
|
1708
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1709
|
+
let timedOut = false;
|
|
1710
|
+
try {
|
|
1711
|
+
await Promise.race([
|
|
1712
|
+
write(data),
|
|
1713
|
+
new Promise<never>((_, reject) => {
|
|
1714
|
+
timer = setTimeout(() => {
|
|
1715
|
+
timedOut = true;
|
|
1716
|
+
reject(
|
|
1717
|
+
ERRORS.TypedError(
|
|
1718
|
+
HardwareErrorCode.BleWriteCharacteristicError,
|
|
1719
|
+
`BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`
|
|
1720
|
+
)
|
|
1721
|
+
);
|
|
1722
|
+
}, BLE_WRITE_PACKET_TIMEOUT_MS);
|
|
1723
|
+
}),
|
|
1724
|
+
]);
|
|
1725
|
+
this.writeTimeoutCounts.delete(uuid);
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
if (timedOut) {
|
|
1728
|
+
// A superseded call's late write must not tear down the link the current
|
|
1729
|
+
// call is using; only the owner of the transport may declare it dead.
|
|
1730
|
+
if (isCurrentOwner && !isCurrentOwner()) {
|
|
1731
|
+
Log?.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
|
|
1732
|
+
} else {
|
|
1733
|
+
this.tearDownWedgedLink(uuid);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
throw error;
|
|
1737
|
+
} finally {
|
|
1738
|
+
if (timer) clearTimeout(timer);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* Drop a link whose writes stopped completing. The JS state is purged synchronously
|
|
1744
|
+
* so the next acquire() cannot reuse the dead transport, while the native teardown is
|
|
1745
|
+
* intentionally NOT awaited: it talks to the very layer that just stopped settling
|
|
1746
|
+
* promises, so awaiting it could hang exactly like the write it is recovering from.
|
|
1747
|
+
*/
|
|
1748
|
+
private tearDownWedgedLink(uuid: string) {
|
|
1749
|
+
const timeouts = (this.writeTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1750
|
+
this.writeTimeoutCounts.set(uuid, timeouts);
|
|
1751
|
+
Log?.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
|
|
1752
|
+
consecutiveWriteTimeouts: timeouts,
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
const wedged = transportCache[uuid];
|
|
1756
|
+
this.disconnect(uuid).catch(error => {
|
|
1757
|
+
Log?.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
|
|
1758
|
+
});
|
|
1759
|
+
if (wedged && transportCache[uuid] === wedged) {
|
|
1760
|
+
delete transportCache[uuid];
|
|
1761
|
+
}
|
|
1762
|
+
this.deviceProtocol.delete(uuid);
|
|
1763
|
+
this.probingProtocols.delete(uuid);
|
|
1764
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
1765
|
+
this.resetProtocolV2Frames(uuid);
|
|
1766
|
+
|
|
1767
|
+
if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1768
|
+
// Reconnecting reuses the same native peripheral object. When it stays wedged
|
|
1769
|
+
// across attempts the poison lives in the BLE manager itself, and only a fresh
|
|
1770
|
+
// manager drops every cached peripheral — the JS equivalent of restarting the app.
|
|
1771
|
+
Log?.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
|
|
1772
|
+
this.resetPlxManager();
|
|
1773
|
+
this.writeTimeoutCounts.delete(uuid);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
private resetPlxManager() {
|
|
1778
|
+
const manager = this.blePlxManager;
|
|
1779
|
+
this.blePlxManager = undefined;
|
|
1780
|
+
// Every cached transport belongs to the destroyed manager's peripherals.
|
|
1781
|
+
Object.keys(transportCache).forEach(key => {
|
|
1782
|
+
delete transportCache[key];
|
|
1783
|
+
});
|
|
1784
|
+
this.deviceProtocol.clear();
|
|
1785
|
+
this.probingProtocols.clear();
|
|
1786
|
+
this.sessionProtocols.clear();
|
|
1787
|
+
this.protocolReprobeFailures.clear();
|
|
1788
|
+
this.monitorTokens.clear();
|
|
1789
|
+
this.protocolV2Assemblers.clear();
|
|
1790
|
+
try {
|
|
1791
|
+
manager?.destroy();
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
Log?.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1513
1797
|
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1514
1798
|
return ERRORS.TypedError(
|
|
1515
1799
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1525,11 +1809,19 @@ export default class ReactNativeBleTransport {
|
|
|
1525
1809
|
}
|
|
1526
1810
|
|
|
1527
1811
|
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1812
|
+
if (this.probingProtocols.get(uuid) === protocol) {
|
|
1813
|
+
this.probingProtocols.delete(uuid);
|
|
1814
|
+
}
|
|
1528
1815
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1529
1816
|
this.deviceProtocol.delete(uuid);
|
|
1530
1817
|
}
|
|
1531
1818
|
}
|
|
1532
1819
|
|
|
1820
|
+
/** Protocol to route a call with: confirmed if known, otherwise the one being probed. */
|
|
1821
|
+
private getActiveProtocol(uuid: string): ProtocolType | undefined {
|
|
1822
|
+
return this.deviceProtocol.get(uuid) ?? this.probingProtocols.get(uuid);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1533
1825
|
private async detectProtocol(
|
|
1534
1826
|
uuid: string,
|
|
1535
1827
|
expectedProtocol?: ProtocolType,
|
|
@@ -1549,6 +1841,7 @@ export default class ReactNativeBleTransport {
|
|
|
1549
1841
|
if (expectedProtocol === 'V1') {
|
|
1550
1842
|
if (await this.probeProtocolV1(uuid)) {
|
|
1551
1843
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1844
|
+
this.sessionProtocols.set(uuid, 'V1');
|
|
1552
1845
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1553
1846
|
deviceId: uuid,
|
|
1554
1847
|
protocol: 'V1',
|
|
@@ -1562,6 +1855,7 @@ export default class ReactNativeBleTransport {
|
|
|
1562
1855
|
if (expectedProtocol === 'V2') {
|
|
1563
1856
|
if (await this.probeProtocolV2(uuid)) {
|
|
1564
1857
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1858
|
+
this.sessionProtocols.set(uuid, 'V2');
|
|
1565
1859
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1566
1860
|
deviceId: uuid,
|
|
1567
1861
|
protocol: 'V2',
|
|
@@ -1574,8 +1868,18 @@ export default class ReactNativeBleTransport {
|
|
|
1574
1868
|
|
|
1575
1869
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1576
1870
|
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1577
|
-
const
|
|
1871
|
+
const sessionProtocol = this.sessionProtocols.get(uuid);
|
|
1872
|
+
const reprobeFailures = this.protocolReprobeFailures.get(uuid) ?? 0;
|
|
1873
|
+
const fullProbeOrder: ProtocolType[] =
|
|
1578
1874
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1875
|
+
// A device that already answered on a protocol in this session keeps answering on
|
|
1876
|
+
// it; while it is rebooting nothing answers at all, so probing the other protocol
|
|
1877
|
+
// only adds its timeout to every poll.
|
|
1878
|
+
const trustSessionProtocol =
|
|
1879
|
+
sessionProtocol !== undefined &&
|
|
1880
|
+
!protocolHint &&
|
|
1881
|
+
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
1882
|
+
const probeOrder: ProtocolType[] = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
1579
1883
|
|
|
1580
1884
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1581
1885
|
const protocol = probeOrder[i];
|
|
@@ -1593,6 +1897,8 @@ export default class ReactNativeBleTransport {
|
|
|
1593
1897
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1594
1898
|
if (detected) {
|
|
1595
1899
|
this.deviceProtocol.set(uuid, protocol);
|
|
1900
|
+
this.sessionProtocols.set(uuid, protocol);
|
|
1901
|
+
this.protocolReprobeFailures.delete(uuid);
|
|
1596
1902
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1597
1903
|
deviceId: uuid,
|
|
1598
1904
|
protocol,
|
|
@@ -1602,7 +1908,16 @@ export default class ReactNativeBleTransport {
|
|
|
1602
1908
|
}
|
|
1603
1909
|
}
|
|
1604
1910
|
|
|
1911
|
+
if (trustSessionProtocol) {
|
|
1912
|
+
// Still silent on its own protocol: count it, and let the streak expire the
|
|
1913
|
+
// shortcut so a device that genuinely switched protocols is found again.
|
|
1914
|
+
this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
|
|
1915
|
+
} else {
|
|
1916
|
+
this.protocolReprobeFailures.delete(uuid);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1605
1919
|
this.deviceProtocol.delete(uuid);
|
|
1920
|
+
this.probingProtocols.delete(uuid);
|
|
1606
1921
|
throw this.createProtocolDetectionError();
|
|
1607
1922
|
}
|
|
1608
1923
|
|
|
@@ -1664,14 +1979,20 @@ export default class ReactNativeBleTransport {
|
|
|
1664
1979
|
}
|
|
1665
1980
|
|
|
1666
1981
|
try {
|
|
1667
|
-
this.
|
|
1982
|
+
this.probingProtocols.set(uuid, 'V1');
|
|
1668
1983
|
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1669
1984
|
// session before Core has a chance to restore a hidden wallet.
|
|
1670
1985
|
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1986
|
+
this.probingProtocols.delete(uuid);
|
|
1671
1987
|
return true;
|
|
1672
1988
|
} catch (error) {
|
|
1673
1989
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1674
1990
|
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1991
|
+
// A wedged write already dropped the link, so probing another protocol on it
|
|
1992
|
+
// would only fail against a torn-down transport: surface the real cause.
|
|
1993
|
+
if (isWedgedWriteError(error)) {
|
|
1994
|
+
throw error;
|
|
1995
|
+
}
|
|
1675
1996
|
return false;
|
|
1676
1997
|
}
|
|
1677
1998
|
}
|
|
@@ -1681,7 +2002,7 @@ export default class ReactNativeBleTransport {
|
|
|
1681
2002
|
return false;
|
|
1682
2003
|
}
|
|
1683
2004
|
|
|
1684
|
-
this.
|
|
2005
|
+
this.probingProtocols.set(uuid, 'V2');
|
|
1685
2006
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1686
2007
|
const detected = await probeProtocolV2Helper({
|
|
1687
2008
|
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
@@ -1696,6 +2017,8 @@ export default class ReactNativeBleTransport {
|
|
|
1696
2017
|
});
|
|
1697
2018
|
if (!detected) {
|
|
1698
2019
|
this.clearProbeProtocol(uuid, 'V2');
|
|
2020
|
+
} else {
|
|
2021
|
+
this.probingProtocols.delete(uuid);
|
|
1699
2022
|
}
|
|
1700
2023
|
return detected;
|
|
1701
2024
|
}
|
|
@@ -1777,17 +2100,15 @@ export default class ReactNativeBleTransport {
|
|
|
1777
2100
|
}
|
|
1778
2101
|
|
|
1779
2102
|
private async writeProtocolV2Packet(
|
|
2103
|
+
uuid: string,
|
|
1780
2104
|
transport: BleTransport,
|
|
1781
2105
|
base64: string,
|
|
1782
2106
|
context: ProtocolV2CallContext,
|
|
1783
2107
|
assertCurrentGeneration: () => void
|
|
1784
2108
|
) {
|
|
1785
|
-
const shouldUseWriteWithResponse =
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
requestedWithResponse: context.writeWithResponse,
|
|
1789
|
-
characteristic: transport.writeCharacteristic,
|
|
1790
|
-
});
|
|
2109
|
+
const shouldUseWriteWithResponse =
|
|
2110
|
+
transport.writeCharacteristic.isWritableWithResponse &&
|
|
2111
|
+
(context.writeWithResponse === true || (Platform.OS === 'ios' && !context.highVolume));
|
|
1791
2112
|
let attempt = 0;
|
|
1792
2113
|
for (;;) {
|
|
1793
2114
|
assertCurrentGeneration();
|
|
@@ -1795,11 +2116,24 @@ export default class ReactNativeBleTransport {
|
|
|
1795
2116
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1796
2117
|
}
|
|
1797
2118
|
try {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2119
|
+
await this.writeBlePacket(
|
|
2120
|
+
uuid,
|
|
2121
|
+
base64,
|
|
2122
|
+
payload =>
|
|
2123
|
+
shouldUseWriteWithResponse
|
|
2124
|
+
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
2125
|
+
: transport.writeCharacteristic.writeWithoutResponse(payload),
|
|
2126
|
+
// Same rule as Protocol V1: a write from a superseded generation must not
|
|
2127
|
+
// tear down the link that the current generation is using.
|
|
2128
|
+
() => {
|
|
2129
|
+
try {
|
|
2130
|
+
assertCurrentGeneration();
|
|
2131
|
+
return !context.signal.aborted;
|
|
2132
|
+
} catch {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
);
|
|
1803
2137
|
assertCurrentGeneration();
|
|
1804
2138
|
return;
|
|
1805
2139
|
} catch (error) {
|
|
@@ -1822,6 +2156,7 @@ export default class ReactNativeBleTransport {
|
|
|
1822
2156
|
}
|
|
1823
2157
|
|
|
1824
2158
|
private async writeProtocolV2Frame(
|
|
2159
|
+
uuid: string,
|
|
1825
2160
|
transport: BleTransport,
|
|
1826
2161
|
frame: Uint8Array,
|
|
1827
2162
|
context: ProtocolV2CallContext,
|
|
@@ -1832,17 +2167,28 @@ export default class ReactNativeBleTransport {
|
|
|
1832
2167
|
platform: Platform.OS,
|
|
1833
2168
|
iosPacketLength: tuning.iosPacketLength,
|
|
1834
2169
|
androidPacketLength: tuning.androidPacketLength,
|
|
1835
|
-
mtu: transport.mtuSize,
|
|
2170
|
+
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1836
2171
|
});
|
|
2172
|
+
// Match Desktop BLE pacing so Pro2 firmware can finish the previous response
|
|
2173
|
+
// before the next single-packet control command is written.
|
|
2174
|
+
const initialDelayMs =
|
|
2175
|
+
Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
2176
|
+
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
2177
|
+
: 0;
|
|
1837
2178
|
await writeProtocolV2BleFrame({
|
|
1838
2179
|
frame,
|
|
1839
2180
|
packetCapacity,
|
|
1840
2181
|
assertActive: assertCurrentGeneration,
|
|
1841
2182
|
signal: context.signal,
|
|
1842
2183
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
2184
|
+
initialDelayMs,
|
|
2185
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
2186
|
+
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
2187
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1843
2188
|
wait: delay,
|
|
1844
2189
|
writePacket: packet =>
|
|
1845
2190
|
this.writeProtocolV2Packet(
|
|
2191
|
+
uuid,
|
|
1846
2192
|
transport,
|
|
1847
2193
|
Buffer.from(packet).toString('base64'),
|
|
1848
2194
|
context,
|
|
@@ -1866,39 +2212,11 @@ export default class ReactNativeBleTransport {
|
|
|
1866
2212
|
|
|
1867
2213
|
if (highVolumeWrite) {
|
|
1868
2214
|
const tuning = getProtocolV2BleTuning();
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
requestedWithResponse: options?.writeWithResponse,
|
|
1874
|
-
characteristic: currentTransport.writeCharacteristic,
|
|
1875
|
-
});
|
|
1876
|
-
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
1877
|
-
platform: Platform.OS,
|
|
1878
|
-
iosPacketLength: tuning.iosPacketLength,
|
|
1879
|
-
androidPacketLength: tuning.androidPacketLength,
|
|
1880
|
-
mtu: currentTransport.mtuSize,
|
|
2215
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
2216
|
+
name,
|
|
2217
|
+
writeMode: options?.writeWithResponse ? 'withResponse' : 'withoutResponse',
|
|
2218
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1881
2219
|
});
|
|
1882
|
-
const writeMode = writeWithResponse ? 'withResponse' : 'withoutResponse';
|
|
1883
|
-
const logSignature = `${name}:${writeMode}:${String(
|
|
1884
|
-
currentTransport.mtuSize
|
|
1885
|
-
)}:${packetCapacity}`;
|
|
1886
|
-
const loggedSignatures =
|
|
1887
|
-
this.protocolV2HighVolumeLogSignatures.get(uuid) ?? new Set<string>();
|
|
1888
|
-
if (!loggedSignatures.has(logSignature)) {
|
|
1889
|
-
loggedSignatures.add(logSignature);
|
|
1890
|
-
this.protocolV2HighVolumeLogSignatures.set(uuid, loggedSignatures);
|
|
1891
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1892
|
-
name,
|
|
1893
|
-
writeMode,
|
|
1894
|
-
reportedMtu: currentTransport.mtuSize,
|
|
1895
|
-
packetCapacity,
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
if (highVolumeWrite) {
|
|
1901
|
-
await this.enableAndroidHighConnectionPriority(uuid);
|
|
1902
2220
|
}
|
|
1903
2221
|
|
|
1904
2222
|
try {
|
|
@@ -1912,73 +2230,6 @@ export default class ReactNativeBleTransport {
|
|
|
1912
2230
|
} catch (e) {
|
|
1913
2231
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1914
2232
|
throw e;
|
|
1915
|
-
} finally {
|
|
1916
|
-
if (highVolumeWrite) {
|
|
1917
|
-
this.scheduleAndroidBalancedConnectionPriority(uuid);
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
private clearAndroidPriorityResetTimer(uuid: string) {
|
|
1923
|
-
const timerId = this.androidPriorityResetTimers.get(uuid);
|
|
1924
|
-
if (timerId !== undefined) {
|
|
1925
|
-
clearTimeout(timerId);
|
|
1926
|
-
this.androidPriorityResetTimers.delete(uuid);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
private async enableAndroidHighConnectionPriority(uuid: string) {
|
|
1931
|
-
if (Platform.OS !== 'android') return;
|
|
1932
|
-
|
|
1933
|
-
this.clearAndroidPriorityResetTimer(uuid);
|
|
1934
|
-
if (this.androidHighPriorityDevices.has(uuid)) return;
|
|
1935
|
-
|
|
1936
|
-
const transport = transportCache[uuid];
|
|
1937
|
-
if (!transport) return;
|
|
1938
|
-
|
|
1939
|
-
try {
|
|
1940
|
-
transport.device = await transport.device.requestConnectionPriority(ConnectionPriority.High);
|
|
1941
|
-
this.androidHighPriorityDevices.add(uuid);
|
|
1942
|
-
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
1943
|
-
priority: 'high',
|
|
1944
|
-
});
|
|
1945
|
-
} catch (error) {
|
|
1946
|
-
Log?.debug('[ReactNativeBleTransport] Android BLE high priority request failed', {
|
|
1947
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
private scheduleAndroidBalancedConnectionPriority(uuid: string) {
|
|
1953
|
-
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.has(uuid)) return;
|
|
1954
|
-
|
|
1955
|
-
this.clearAndroidPriorityResetTimer(uuid);
|
|
1956
|
-
const timerId = setTimeout(() => {
|
|
1957
|
-
this.androidPriorityResetTimers.delete(uuid);
|
|
1958
|
-
this.restoreAndroidConnectionPriority(uuid, transportCache[uuid]).catch(error =>
|
|
1959
|
-
Log?.debug('[ReactNativeBleTransport] Android BLE priority restore failed', error)
|
|
1960
|
-
);
|
|
1961
|
-
}, ANDROID_HIGH_PRIORITY_IDLE_MS);
|
|
1962
|
-
this.androidPriorityResetTimers.set(uuid, timerId);
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
private async restoreAndroidConnectionPriority(uuid: string, transport?: BleTransport) {
|
|
1966
|
-
this.clearAndroidPriorityResetTimer(uuid);
|
|
1967
|
-
if (Platform.OS !== 'android' || !this.androidHighPriorityDevices.delete(uuid) || !transport) {
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
try {
|
|
1972
|
-
transport.device = await transport.device.requestConnectionPriority(
|
|
1973
|
-
ConnectionPriority.Balanced
|
|
1974
|
-
);
|
|
1975
|
-
Log?.debug('[ReactNativeBleTransport] Android BLE connection priority changed', {
|
|
1976
|
-
priority: 'balanced',
|
|
1977
|
-
});
|
|
1978
|
-
} catch (error) {
|
|
1979
|
-
Log?.debug('[ReactNativeBleTransport] Android BLE balanced priority request failed', {
|
|
1980
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1981
|
-
});
|
|
1982
2233
|
}
|
|
1983
2234
|
}
|
|
1984
2235
|
|
|
@@ -2002,7 +2253,13 @@ export default class ReactNativeBleTransport {
|
|
|
2002
2253
|
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
2003
2254
|
assertCurrentGeneration();
|
|
2004
2255
|
const currentTransport = this.getCachedTransport(uuid);
|
|
2005
|
-
await this.writeProtocolV2Frame(
|
|
2256
|
+
await this.writeProtocolV2Frame(
|
|
2257
|
+
uuid,
|
|
2258
|
+
currentTransport,
|
|
2259
|
+
frame,
|
|
2260
|
+
context,
|
|
2261
|
+
assertCurrentGeneration
|
|
2262
|
+
);
|
|
2006
2263
|
},
|
|
2007
2264
|
readFrame: async () => {
|
|
2008
2265
|
assertCurrentGeneration();
|
|
@@ -2027,6 +2284,6 @@ export default class ReactNativeBleTransport {
|
|
|
2027
2284
|
}
|
|
2028
2285
|
|
|
2029
2286
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
2030
|
-
return this.
|
|
2287
|
+
return this.getActiveProtocol(path);
|
|
2031
2288
|
}
|
|
2032
2289
|
}
|