@onekeyfe/hd-transport-react-native 1.2.0-alpha.67 → 1.2.0-alpha.69
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 +6 -0
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +9 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +271 -343
- package/jest.config.js +0 -5
- package/package.json +5 -5
- package/src/BleTransport.ts +7 -1
- package/src/__tests__/BleTransport.test.ts +54 -2
- package/src/__tests__/bleStrategy.test.ts +77 -7
- package/src/__tests__/enumerate.test.ts +132 -0
- package/src/__tests__/protocolV2Link.test.ts +384 -49
- package/src/bleStrategy.ts +23 -10
- package/src/constants.ts +2 -1
- package/src/index.ts +290 -476
- package/src/__tests__/connectTimeout.test.ts +0 -281
- package/src/__tests__/protocolReprobe.test.ts +0 -132
- package/src/__tests__/protocolV1SchemaFixture.ts +0 -39
- package/src/__tests__/staleCallTimeout.test.ts +0 -210
- package/src/__tests__/writePacketTimeout.test.ts +0 -305
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BleError,
|
|
6
6
|
BleErrorCode,
|
|
7
7
|
BleManager as BlePlxManager,
|
|
8
|
+
ConnectionPriority,
|
|
8
9
|
ScanMode,
|
|
9
10
|
} from 'react-native-ble-plx';
|
|
10
11
|
import ByteBuffer from 'bytebuffer';
|
|
@@ -28,14 +29,21 @@ import {
|
|
|
28
29
|
HardwareErrorCode,
|
|
29
30
|
createDeferred,
|
|
30
31
|
isOnekeyBluetoothDevice,
|
|
32
|
+
isPro2FindMyAdvertisementName,
|
|
31
33
|
} from '@onekeyfe/hd-shared';
|
|
32
34
|
|
|
33
35
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
34
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
hasWritableCapability,
|
|
38
|
+
resolveProtocolV2PacketCapacity,
|
|
39
|
+
shouldWriteProtocolV2WithResponse,
|
|
40
|
+
} from './bleStrategy';
|
|
35
41
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
36
42
|
import {
|
|
37
43
|
ANDROID_PACKET_LENGTH,
|
|
44
|
+
ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
38
45
|
IOS_PACKET_LENGTH,
|
|
46
|
+
IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
39
47
|
getBluetoothServiceUuids,
|
|
40
48
|
getInfosForServiceUuid,
|
|
41
49
|
isSameBleUuid,
|
|
@@ -71,6 +79,33 @@ type ResolvedBleCharacteristics = {
|
|
|
71
79
|
notifyCharacteristic: Characteristic;
|
|
72
80
|
};
|
|
73
81
|
|
|
82
|
+
const isAsciiWhitespace = (code: number) =>
|
|
83
|
+
code === 0x09 ||
|
|
84
|
+
code === 0x0a ||
|
|
85
|
+
code === 0x0b ||
|
|
86
|
+
code === 0x0c ||
|
|
87
|
+
code === 0x0d ||
|
|
88
|
+
code === 0x20;
|
|
89
|
+
|
|
90
|
+
const hasGattCongestedStatus = (text: string) => {
|
|
91
|
+
let searchFrom = 0;
|
|
92
|
+
while (searchFrom < text.length) {
|
|
93
|
+
const statusIndex = text.indexOf('status', searchFrom);
|
|
94
|
+
if (statusIndex < 0) return false;
|
|
95
|
+
|
|
96
|
+
let cursor = statusIndex + 'status'.length;
|
|
97
|
+
while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor))) cursor += 1;
|
|
98
|
+
if (text[cursor] === ':' || text[cursor] === '=') {
|
|
99
|
+
cursor += 1;
|
|
100
|
+
while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor))) cursor += 1;
|
|
101
|
+
}
|
|
102
|
+
if (text.startsWith(String(ANDROID_GATT_CONGESTED_STATUS), cursor)) return true;
|
|
103
|
+
|
|
104
|
+
searchFrom = statusIndex + 'status'.length;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
|
|
74
109
|
const delay = (ms: number) =>
|
|
75
110
|
new Promise<void>(resolve => {
|
|
76
111
|
setTimeout(resolve, ms);
|
|
@@ -99,29 +134,13 @@ export const getFirmwareUploadWriteRetryType = (
|
|
|
99
134
|
const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
|
|
100
135
|
.filter(value => typeof value === 'string')
|
|
101
136
|
.join(' ');
|
|
102
|
-
return
|
|
137
|
+
return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
|
|
103
138
|
};
|
|
104
139
|
|
|
105
140
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
106
141
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
107
142
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
108
143
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
109
|
-
/**
|
|
110
|
-
* Per-packet write budget. iOS only resolves writeWithoutResponse once CoreBluetooth
|
|
111
|
-
* reports the peripheral ready again; a peripheral wedged by its own firmware reboot
|
|
112
|
-
* stops reporting ready while staying connected, so the write promise never settles.
|
|
113
|
-
* Response timeouts cannot cover that — they are armed after the writes complete —
|
|
114
|
-
* and an unbounded write leaves the whole transport unusable until the process dies.
|
|
115
|
-
* A healthy packet completes in milliseconds, so this only fires on a dead link.
|
|
116
|
-
*/
|
|
117
|
-
export const BLE_WRITE_PACKET_TIMEOUT_MS = 10_000;
|
|
118
|
-
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
119
|
-
const isWedgedWriteError = (error: unknown): boolean =>
|
|
120
|
-
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleWriteCharacteristicError &&
|
|
121
|
-
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
122
|
-
(error as { message: string }).message.startsWith(WEDGED_WRITE_MESSAGE);
|
|
123
|
-
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
124
|
-
export const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
125
144
|
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
126
145
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
127
146
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
@@ -133,8 +152,8 @@ export type ProtocolV2BleTuning = {
|
|
|
133
152
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
134
153
|
|
|
135
154
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
136
|
-
iosPacketLength:
|
|
137
|
-
androidPacketLength:
|
|
155
|
+
iosPacketLength: IOS_PROTOCOL_V2_PACKET_LENGTH,
|
|
156
|
+
androidPacketLength: ANDROID_PROTOCOL_V2_PACKET_LENGTH,
|
|
138
157
|
};
|
|
139
158
|
|
|
140
159
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -176,54 +195,21 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
176
195
|
return device?.name || device?.localName || null;
|
|
177
196
|
}
|
|
178
197
|
|
|
179
|
-
const
|
|
198
|
+
const IOS_REQUEST_MTU = 247;
|
|
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;
|
|
180
203
|
|
|
181
|
-
const
|
|
204
|
+
const getRequestedBleMtu = () =>
|
|
205
|
+
Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
182
206
|
|
|
183
207
|
const connectOptions: Record<string, unknown> = {
|
|
184
|
-
requestMTU:
|
|
185
|
-
timeout:
|
|
208
|
+
requestMTU: getRequestedBleMtu(),
|
|
209
|
+
timeout: 3000,
|
|
186
210
|
refreshGatt: 'OnConnected',
|
|
187
211
|
};
|
|
188
212
|
|
|
189
|
-
/** Fallback connect options: drops requestMTU (the thing being worked around) but keeps the native budget. */
|
|
190
|
-
const fallbackConnectOptions: Record<string, unknown> = {
|
|
191
|
-
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* JS backstop for connect. The native adapter applies its own 3s budget, but it
|
|
196
|
-
* schedules that timeout on its serial queue, so a busy queue (e.g. right after a
|
|
197
|
-
* firmware install tears the link down) can leave the promise unsettled — observed
|
|
198
|
-
* blocking a reconnect for 61s until the app-level timeout. Healthy connects finish
|
|
199
|
-
* inside the native budget, so this only fires when the native timeout did not.
|
|
200
|
-
*/
|
|
201
|
-
export const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
202
|
-
/**
|
|
203
|
-
* Service discovery and characteristic resolution run after connect() succeeds, but
|
|
204
|
-
* CoreBluetooth schedules them on the same serial queue. If that queue is wedged by a
|
|
205
|
-
* device reboot, these calls can remain pending forever unless they have their own
|
|
206
|
-
* budget.
|
|
207
|
-
*/
|
|
208
|
-
export const BLE_GATT_SETUP_TIMEOUT_MS = 10_000;
|
|
209
|
-
/**
|
|
210
|
-
* How many times a known device may fail its own protocol before we probe the others
|
|
211
|
-
* again. Reconnect polling during a device reboot repeats this every few seconds, and
|
|
212
|
-
* probing Protocol V2 costs a 10s Ping timeout, so paying it on every attempt for a
|
|
213
|
-
* device we just spoke V1 to dominates the wait. A firmware update can legitimately
|
|
214
|
-
* change a device's protocol, so the shortcut has to expire rather than stick.
|
|
215
|
-
*/
|
|
216
|
-
export const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
217
|
-
/** BLE setup timeouts since the last successful setup before the manager is recreated. */
|
|
218
|
-
export const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
219
|
-
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
220
|
-
const isConnectTimeoutError = (error: unknown): boolean =>
|
|
221
|
-
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleConnectedError &&
|
|
222
|
-
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
223
|
-
(error as { message: string }).message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
224
|
-
const isNativeOperationTimeoutError = (error: unknown): boolean =>
|
|
225
|
-
(error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
|
|
226
|
-
|
|
227
213
|
export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
228
214
|
|
|
229
215
|
const tryToGetConfiguration = (device: Device) => {
|
|
@@ -235,23 +221,32 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
235
221
|
return infos;
|
|
236
222
|
};
|
|
237
223
|
|
|
238
|
-
const
|
|
239
|
-
|
|
224
|
+
const requestNegotiatedMtu = async (
|
|
225
|
+
device: Device,
|
|
226
|
+
stage: 'connected' | 'servicesAndNotifyReady',
|
|
227
|
+
attempt: number
|
|
228
|
+
) => {
|
|
229
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
240
230
|
|
|
241
231
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
requested: ANDROID_REQUEST_MTU,
|
|
246
|
-
actual: mtuDevice.mtu,
|
|
247
|
-
});
|
|
232
|
+
// iOS ignores the requested value but react-native-ble-plx returns a fresh
|
|
233
|
+
// Device snapshot whose MTU is derived from CoreBluetooth's maximum write length.
|
|
234
|
+
const mtuDevice = await device.requestMTU(getRequestedBleMtu());
|
|
248
235
|
return mtuDevice;
|
|
249
236
|
} catch (error) {
|
|
250
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
237
|
+
Log?.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
238
|
+
platform: Platform.OS,
|
|
239
|
+
stage,
|
|
240
|
+
attempt,
|
|
241
|
+
actual: device.mtu,
|
|
242
|
+
error: error instanceof Error ? error.message : String(error),
|
|
243
|
+
});
|
|
251
244
|
return device;
|
|
252
245
|
}
|
|
253
246
|
};
|
|
254
247
|
|
|
248
|
+
const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
249
|
+
|
|
255
250
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
256
251
|
|
|
257
252
|
function remapError(error: IOBleErrorRemap) {
|
|
@@ -312,28 +307,8 @@ export default class ReactNativeBleTransport {
|
|
|
312
307
|
/** Per-device protocol type detected by active wire-level probe after connect. */
|
|
313
308
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
314
309
|
|
|
315
|
-
/**
|
|
316
|
-
* Protocol a probe is currently trying, before the device has confirmed it. Calls
|
|
317
|
-
* must route with it, but acquire() must not treat it as a detected protocol: a
|
|
318
|
-
* probe that never answers would otherwise leave the reuse fast path handing out a
|
|
319
|
-
* transport that was never validated.
|
|
320
|
-
*/
|
|
321
|
-
private probingProtocols: Map<string, ProtocolType> = new Map();
|
|
322
|
-
|
|
323
|
-
/** Consecutive write timeouts per device; reset by any write that completes. */
|
|
324
|
-
private writeTimeoutCounts: Map<string, number> = new Map();
|
|
325
|
-
|
|
326
|
-
/** BLE setup timeouts per device since the last complete characteristic resolution. */
|
|
327
|
-
private connectionSetupTimeoutCounts: Map<string, number> = new Map();
|
|
328
|
-
|
|
329
310
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
330
311
|
|
|
331
|
-
/** Protocol this device actually answered on, kept across reconnects of one session. */
|
|
332
|
-
private sessionProtocols: Map<string, ProtocolType> = new Map();
|
|
333
|
-
|
|
334
|
-
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
335
|
-
private protocolReprobeFailures: Map<string, number> = new Map();
|
|
336
|
-
|
|
337
312
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
338
313
|
|
|
339
314
|
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
@@ -365,6 +340,12 @@ export default class ReactNativeBleTransport {
|
|
|
365
340
|
|
|
366
341
|
private disconnectEventTokens: Map<string, number> = new Map();
|
|
367
342
|
|
|
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
|
+
|
|
368
349
|
private nextMonitorToken = 1;
|
|
369
350
|
|
|
370
351
|
constructor(options: TransportOptions) {
|
|
@@ -551,21 +532,22 @@ export default class ReactNativeBleTransport {
|
|
|
551
532
|
const isConnected = await device.isConnected().catch(() => false);
|
|
552
533
|
if (!isConnected) {
|
|
553
534
|
try {
|
|
554
|
-
device = await
|
|
535
|
+
device = await device.connect(connectOptions);
|
|
555
536
|
} catch (e) {
|
|
556
537
|
if (
|
|
557
538
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
558
539
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
559
540
|
) {
|
|
560
|
-
device = await
|
|
541
|
+
device = await device.connect();
|
|
561
542
|
} else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
|
|
562
543
|
throw e;
|
|
563
544
|
}
|
|
564
545
|
}
|
|
565
546
|
}
|
|
566
547
|
|
|
567
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
568
|
-
|
|
548
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
549
|
+
device
|
|
550
|
+
);
|
|
569
551
|
|
|
570
552
|
transport.device = device;
|
|
571
553
|
transport.writeCharacteristic = writeCharacteristic;
|
|
@@ -655,12 +637,21 @@ export default class ReactNativeBleTransport {
|
|
|
655
637
|
}
|
|
656
638
|
|
|
657
639
|
const displayName = getDeviceDisplayName(device);
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
640
|
+
// iOS may report a service-only advertisement before the named scan response.
|
|
641
|
+
// Do not cache that incomplete advertisement as an unknown device.
|
|
642
|
+
const isUnnamedIOSPeripheral = Platform.OS === 'ios' && !displayName?.trim();
|
|
643
|
+
const isFindMyPeripheral =
|
|
644
|
+
isPro2FindMyAdvertisementName(device?.name) ||
|
|
645
|
+
isPro2FindMyAdvertisementName(device?.localName);
|
|
646
|
+
const isOneKey =
|
|
647
|
+
!isUnnamedIOSPeripheral &&
|
|
648
|
+
!isFindMyPeripheral &&
|
|
649
|
+
isOnekeyBluetoothDevice({
|
|
650
|
+
id: device?.id,
|
|
651
|
+
name: device?.name,
|
|
652
|
+
localName: device?.localName,
|
|
653
|
+
serviceUuids: device?.serviceUUIDs,
|
|
654
|
+
});
|
|
664
655
|
if (isOneKey) {
|
|
665
656
|
addDevice(device as unknown as Device);
|
|
666
657
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
@@ -681,7 +672,12 @@ export default class ReactNativeBleTransport {
|
|
|
681
672
|
'localName' in device && typeof device.localName === 'string'
|
|
682
673
|
? device.localName
|
|
683
674
|
: null;
|
|
675
|
+
const isFindMyPeripheral =
|
|
676
|
+
isPro2FindMyAdvertisementName(device.name) ||
|
|
677
|
+
isPro2FindMyAdvertisementName(localName);
|
|
678
|
+
|
|
684
679
|
if (
|
|
680
|
+
!isFindMyPeripheral &&
|
|
685
681
|
isOnekeyBluetoothDevice({
|
|
686
682
|
id: device.id,
|
|
687
683
|
name: device.name,
|
|
@@ -730,11 +726,9 @@ export default class ReactNativeBleTransport {
|
|
|
730
726
|
characteristics?: ResolvedBleCharacteristics
|
|
731
727
|
) {
|
|
732
728
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
733
|
-
characteristics ?? (await this.
|
|
729
|
+
characteristics ?? (await this.resolveCharacteristics(device));
|
|
734
730
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
735
|
-
|
|
736
|
-
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
737
|
-
}
|
|
731
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
|
|
738
732
|
const monitorToken = this.nextMonitorToken;
|
|
739
733
|
this.nextMonitorToken += 1;
|
|
740
734
|
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
@@ -748,6 +742,7 @@ export default class ReactNativeBleTransport {
|
|
|
748
742
|
notifyTransactionId
|
|
749
743
|
);
|
|
750
744
|
transportCache[uuid] = transport;
|
|
745
|
+
this.protocolV2HighVolumeLogSignatures.set(uuid, new Set());
|
|
751
746
|
this.protocolV2Assemblers.set(
|
|
752
747
|
uuid,
|
|
753
748
|
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
@@ -761,6 +756,40 @@ export default class ReactNativeBleTransport {
|
|
|
761
756
|
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
762
757
|
}
|
|
763
758
|
|
|
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
|
+
|
|
764
793
|
return transport;
|
|
765
794
|
}
|
|
766
795
|
|
|
@@ -834,22 +863,15 @@ export default class ReactNativeBleTransport {
|
|
|
834
863
|
if (!device) {
|
|
835
864
|
Log?.debug('try to connect to device: ', uuid);
|
|
836
865
|
try {
|
|
837
|
-
device = await
|
|
838
|
-
blePlxManager.connectToDevice(uuid, connectOptions)
|
|
839
|
-
);
|
|
866
|
+
device = await blePlxManager.connectToDevice(uuid, connectOptions);
|
|
840
867
|
} catch (e) {
|
|
841
868
|
Log?.debug('try to connect to device has error: ', e);
|
|
842
|
-
if (isConnectTimeoutError(e)) {
|
|
843
|
-
throw e;
|
|
844
|
-
}
|
|
845
869
|
if (
|
|
846
870
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
847
871
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
848
872
|
) {
|
|
849
873
|
Log?.debug('first try to reconnect without params');
|
|
850
|
-
device = await
|
|
851
|
-
blePlxManager.connectToDevice(uuid, fallbackConnectOptions)
|
|
852
|
-
);
|
|
874
|
+
device = await blePlxManager.connectToDevice(uuid);
|
|
853
875
|
} else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
|
|
854
876
|
Log?.debug('device already connected');
|
|
855
877
|
throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
|
|
@@ -865,36 +887,26 @@ export default class ReactNativeBleTransport {
|
|
|
865
887
|
|
|
866
888
|
if (!(await device.isConnected())) {
|
|
867
889
|
Log?.debug('not connected, try to connect to device: ', uuid);
|
|
868
|
-
const disconnectedDevice = device;
|
|
869
890
|
|
|
870
891
|
try {
|
|
871
|
-
device = await
|
|
872
|
-
disconnectedDevice.connect(connectOptions)
|
|
873
|
-
);
|
|
892
|
+
device = await device.connect(connectOptions);
|
|
874
893
|
} catch (e) {
|
|
875
894
|
Log?.debug('not connected, try to connect to device has error: ', e);
|
|
876
|
-
if (isConnectTimeoutError(e)) {
|
|
877
|
-
throw e;
|
|
878
|
-
}
|
|
879
895
|
if (
|
|
880
896
|
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
881
897
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
882
898
|
) {
|
|
883
899
|
Log?.debug('second try to reconnect without params');
|
|
884
900
|
try {
|
|
885
|
-
device = await
|
|
886
|
-
disconnectedDevice.connect(fallbackConnectOptions)
|
|
887
|
-
);
|
|
901
|
+
device = await device.connect();
|
|
888
902
|
} catch (e) {
|
|
889
903
|
Log?.debug('last try to reconnect error: ', e);
|
|
890
904
|
// last try to reconnect device if this issue exists
|
|
891
905
|
// https://github.com/dotintent/react-native-ble-plx/issues/426
|
|
892
906
|
if (e.errorCode === BleErrorCode.OperationCancelled) {
|
|
893
907
|
Log?.debug('last try to reconnect');
|
|
894
|
-
await
|
|
895
|
-
device = await
|
|
896
|
-
disconnectedDevice.connect(fallbackConnectOptions)
|
|
897
|
-
);
|
|
908
|
+
await device.cancelConnection();
|
|
909
|
+
device = await device.connect();
|
|
898
910
|
}
|
|
899
911
|
}
|
|
900
912
|
} else {
|
|
@@ -903,10 +915,11 @@ export default class ReactNativeBleTransport {
|
|
|
903
915
|
}
|
|
904
916
|
}
|
|
905
917
|
|
|
906
|
-
device = await
|
|
918
|
+
device = await resolveNegotiatedMtu(device);
|
|
907
919
|
const acquiredDevice = device;
|
|
908
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
909
|
-
|
|
920
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
921
|
+
acquiredDevice
|
|
922
|
+
);
|
|
910
923
|
|
|
911
924
|
const protocolHint = expectedProtocol
|
|
912
925
|
? undefined
|
|
@@ -938,7 +951,7 @@ export default class ReactNativeBleTransport {
|
|
|
938
951
|
if (!currentTransport) {
|
|
939
952
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
940
953
|
}
|
|
941
|
-
this.attachDisconnectSubscription(currentTransport,
|
|
954
|
+
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
942
955
|
return { uuid, protocolType };
|
|
943
956
|
} catch (error) {
|
|
944
957
|
await this.release(uuid, true);
|
|
@@ -970,7 +983,7 @@ export default class ReactNativeBleTransport {
|
|
|
970
983
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
971
984
|
return;
|
|
972
985
|
}
|
|
973
|
-
if (this.
|
|
986
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
974
987
|
let errorCode:
|
|
975
988
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
976
989
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -1040,7 +1053,7 @@ export default class ReactNativeBleTransport {
|
|
|
1040
1053
|
|
|
1041
1054
|
try {
|
|
1042
1055
|
const data = Buffer.from(c.value as string, 'base64');
|
|
1043
|
-
const protocol = this.
|
|
1056
|
+
const protocol = this.deviceProtocol.get(uuid);
|
|
1044
1057
|
if (!protocol) {
|
|
1045
1058
|
Log?.debug('monitor data ignored before protocol detection: ', uuid);
|
|
1046
1059
|
return;
|
|
@@ -1074,7 +1087,7 @@ export default class ReactNativeBleTransport {
|
|
|
1074
1087
|
} catch (error) {
|
|
1075
1088
|
Log?.debug('monitor data error: ', error);
|
|
1076
1089
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1077
|
-
if (this.
|
|
1090
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1078
1091
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1079
1092
|
} else if (this.runPromiseDeviceId === uuid) {
|
|
1080
1093
|
this.runPromise?.reject(notifyError);
|
|
@@ -1108,6 +1121,8 @@ export default class ReactNativeBleTransport {
|
|
|
1108
1121
|
return Promise.resolve(true);
|
|
1109
1122
|
}
|
|
1110
1123
|
|
|
1124
|
+
await this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1125
|
+
|
|
1111
1126
|
if (transport) {
|
|
1112
1127
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1113
1128
|
this.monitorTokens.delete(uuid);
|
|
@@ -1137,8 +1152,9 @@ export default class ReactNativeBleTransport {
|
|
|
1137
1152
|
delete transportCache[uuid];
|
|
1138
1153
|
}
|
|
1139
1154
|
|
|
1155
|
+
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1156
|
+
|
|
1140
1157
|
this.deviceProtocol.delete(uuid);
|
|
1141
|
-
this.probingProtocols.delete(uuid);
|
|
1142
1158
|
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
1143
1159
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1144
1160
|
this.protocolV2Assemblers.delete(uuid);
|
|
@@ -1205,25 +1221,8 @@ export default class ReactNativeBleTransport {
|
|
|
1205
1221
|
const transport = this.getCachedTransport(uuid);
|
|
1206
1222
|
const runPromise = createDeferred<string>();
|
|
1207
1223
|
runPromise.promise.catch(() => undefined);
|
|
1208
|
-
const supersededRunPromise = this.runPromise;
|
|
1209
|
-
if (supersededRunPromise) {
|
|
1210
|
-
// Only forceRun calls (Initialize/Cancel) reach here with a pending call. Settle
|
|
1211
|
-
// the superseded deferred now so its response race resolves and its finally block
|
|
1212
|
-
// clears its timeout timer; an orphaned timer would otherwise fire much later and
|
|
1213
|
-
// tear down the shared connection while another call is using it.
|
|
1214
|
-
supersededRunPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
1215
|
-
}
|
|
1216
1224
|
this.runPromise = runPromise;
|
|
1217
1225
|
this.runPromiseDeviceId = uuid;
|
|
1218
|
-
// A superseded call's late write failure must not clear the successor's ownership;
|
|
1219
|
-
// only the call that still owns the slot may release it.
|
|
1220
|
-
const releaseOwnershipIfCurrent = () => {
|
|
1221
|
-
if (this.runPromise === runPromise) {
|
|
1222
|
-
this.runPromise = null;
|
|
1223
|
-
this.runPromiseDeviceId = null;
|
|
1224
|
-
}
|
|
1225
|
-
};
|
|
1226
|
-
const isCurrentOwner = () => this.runPromise === runPromise;
|
|
1227
1226
|
const messages = this._messages;
|
|
1228
1227
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1229
1228
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1249,9 +1248,6 @@ export default class ReactNativeBleTransport {
|
|
|
1249
1248
|
chunk = ByteBuffer.allocate(packetCapacity);
|
|
1250
1249
|
} catch (e) {
|
|
1251
1250
|
onError(e);
|
|
1252
|
-
if (isWedgedWriteError(e)) {
|
|
1253
|
-
throw e;
|
|
1254
|
-
}
|
|
1255
1251
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1256
1252
|
}
|
|
1257
1253
|
}
|
|
@@ -1283,9 +1279,6 @@ export default class ReactNativeBleTransport {
|
|
|
1283
1279
|
}
|
|
1284
1280
|
} catch (e) {
|
|
1285
1281
|
onError(e);
|
|
1286
|
-
if (isWedgedWriteError(e)) {
|
|
1287
|
-
throw e;
|
|
1288
|
-
}
|
|
1289
1282
|
throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1290
1283
|
}
|
|
1291
1284
|
}
|
|
@@ -1299,15 +1292,9 @@ export default class ReactNativeBleTransport {
|
|
|
1299
1292
|
if (name === 'EmmcFileWrite') {
|
|
1300
1293
|
await writeChunkedData(
|
|
1301
1294
|
buffers,
|
|
1302
|
-
data =>
|
|
1303
|
-
this.writeBlePacket(
|
|
1304
|
-
uuid,
|
|
1305
|
-
data,
|
|
1306
|
-
payload => transport.writeWithRetry(payload),
|
|
1307
|
-
isCurrentOwner
|
|
1308
|
-
),
|
|
1295
|
+
data => transport.writeWithRetry(data),
|
|
1309
1296
|
e => {
|
|
1310
|
-
|
|
1297
|
+
this.runPromise = null;
|
|
1311
1298
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1312
1299
|
}
|
|
1313
1300
|
);
|
|
@@ -1328,12 +1315,7 @@ export default class ReactNativeBleTransport {
|
|
|
1328
1315
|
// eslint-disable-next-line no-constant-condition
|
|
1329
1316
|
while (true) {
|
|
1330
1317
|
try {
|
|
1331
|
-
await
|
|
1332
|
-
uuid,
|
|
1333
|
-
data,
|
|
1334
|
-
payload => transport.writeCharacteristic.writeWithoutResponse(payload),
|
|
1335
|
-
isCurrentOwner
|
|
1336
|
-
);
|
|
1318
|
+
await transport.writeWithRetry(data);
|
|
1337
1319
|
return;
|
|
1338
1320
|
} catch (error) {
|
|
1339
1321
|
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
@@ -1352,7 +1334,7 @@ export default class ReactNativeBleTransport {
|
|
|
1352
1334
|
}
|
|
1353
1335
|
},
|
|
1354
1336
|
e => {
|
|
1355
|
-
|
|
1337
|
+
this.runPromise = null;
|
|
1356
1338
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1357
1339
|
}
|
|
1358
1340
|
);
|
|
@@ -1361,18 +1343,16 @@ export default class ReactNativeBleTransport {
|
|
|
1361
1343
|
const outData = o.toString('base64');
|
|
1362
1344
|
// Upload resources on low-end phones may OOM
|
|
1363
1345
|
try {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1346
|
+
const shouldUseWriteWithResponse =
|
|
1347
|
+
Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1348
|
+
if (shouldUseWriteWithResponse) {
|
|
1349
|
+
await transport.writeCharacteristic.writeWithResponse(outData);
|
|
1350
|
+
} else {
|
|
1351
|
+
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1352
|
+
}
|
|
1370
1353
|
} catch (e) {
|
|
1371
1354
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1372
|
-
|
|
1373
|
-
if (isWedgedWriteError(e)) {
|
|
1374
|
-
throw e;
|
|
1375
|
-
}
|
|
1355
|
+
this.runPromise = null;
|
|
1376
1356
|
if (e.errorCode === BleErrorCode.DeviceDisconnected) {
|
|
1377
1357
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
|
|
1378
1358
|
} else if (e.errorCode === BleErrorCode.OperationStartFailed) {
|
|
@@ -1415,13 +1395,8 @@ export default class ReactNativeBleTransport {
|
|
|
1415
1395
|
}
|
|
1416
1396
|
const isProbeTimeout =
|
|
1417
1397
|
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1418
|
-
// A call that has been superseded (forceRun) or cleaned up no longer owns the
|
|
1419
|
-
// transport; its late timeout must not tear down the connection the current
|
|
1420
|
-
// call is actively using.
|
|
1421
|
-
const isStaleCall = this.runPromise !== runPromise;
|
|
1422
1398
|
if (
|
|
1423
1399
|
!isProbeTimeout &&
|
|
1424
|
-
!isStaleCall &&
|
|
1425
1400
|
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1426
1401
|
) {
|
|
1427
1402
|
await this.disconnect(uuid);
|
|
@@ -1501,10 +1476,7 @@ export default class ReactNativeBleTransport {
|
|
|
1501
1476
|
delete transportCache[session];
|
|
1502
1477
|
}
|
|
1503
1478
|
this.deviceProtocol.delete(session);
|
|
1504
|
-
this.probingProtocols.delete(session);
|
|
1505
1479
|
this.deviceProtocolHints.delete(session);
|
|
1506
|
-
this.sessionProtocols.delete(session);
|
|
1507
|
-
this.protocolReprobeFailures.delete(session);
|
|
1508
1480
|
this.protocolV2Assemblers.delete(session);
|
|
1509
1481
|
this.resetProtocolV2Frames(session);
|
|
1510
1482
|
|
|
@@ -1530,113 +1502,6 @@ export default class ReactNativeBleTransport {
|
|
|
1530
1502
|
this.runPromiseDeviceId = null;
|
|
1531
1503
|
}
|
|
1532
1504
|
|
|
1533
|
-
/** Run a native connect under the JS backstop budget. */
|
|
1534
|
-
private async connectWithTimeout<T>(uuid: string, connect: () => Promise<T>): Promise<T> {
|
|
1535
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1536
|
-
let timedOut = false;
|
|
1537
|
-
const pending = connect();
|
|
1538
|
-
// The abandoned attempt keeps running; swallow its late outcome so it cannot
|
|
1539
|
-
// surface as an unhandled rejection after we have already given up on it.
|
|
1540
|
-
pending.catch(() => undefined);
|
|
1541
|
-
try {
|
|
1542
|
-
const result = await Promise.race([
|
|
1543
|
-
pending,
|
|
1544
|
-
new Promise<never>((_, reject) => {
|
|
1545
|
-
timer = setTimeout(() => {
|
|
1546
|
-
timedOut = true;
|
|
1547
|
-
reject(
|
|
1548
|
-
ERRORS.TypedError(
|
|
1549
|
-
HardwareErrorCode.BleConnectedError,
|
|
1550
|
-
`BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`
|
|
1551
|
-
)
|
|
1552
|
-
);
|
|
1553
|
-
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1554
|
-
}),
|
|
1555
|
-
]);
|
|
1556
|
-
return result;
|
|
1557
|
-
} catch (error) {
|
|
1558
|
-
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1559
|
-
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1560
|
-
}
|
|
1561
|
-
throw error;
|
|
1562
|
-
} finally {
|
|
1563
|
-
if (timer) clearTimeout(timer);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
/** Resolve the complete GATT shape under a budget so acquire() always settles. */
|
|
1568
|
-
private async resolveCharacteristicsWithTimeout(
|
|
1569
|
-
uuid: string,
|
|
1570
|
-
device: Device
|
|
1571
|
-
): Promise<ResolvedBleCharacteristics> {
|
|
1572
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1573
|
-
let timedOut = false;
|
|
1574
|
-
const pending = this.resolveCharacteristics(device);
|
|
1575
|
-
pending.catch(() => undefined);
|
|
1576
|
-
try {
|
|
1577
|
-
const result = await Promise.race([
|
|
1578
|
-
pending,
|
|
1579
|
-
new Promise<never>((_, reject) => {
|
|
1580
|
-
timer = setTimeout(() => {
|
|
1581
|
-
timedOut = true;
|
|
1582
|
-
reject(
|
|
1583
|
-
ERRORS.TypedError(
|
|
1584
|
-
HardwareErrorCode.BleConnectedError,
|
|
1585
|
-
`BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`
|
|
1586
|
-
)
|
|
1587
|
-
);
|
|
1588
|
-
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1589
|
-
}),
|
|
1590
|
-
]);
|
|
1591
|
-
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1592
|
-
return result;
|
|
1593
|
-
} catch (error) {
|
|
1594
|
-
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1595
|
-
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1596
|
-
}
|
|
1597
|
-
throw error;
|
|
1598
|
-
} finally {
|
|
1599
|
-
if (timer) clearTimeout(timer);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
/**
|
|
1604
|
-
* Give up on a BLE setup operation the native layer did not settle. The abandoned
|
|
1605
|
-
* operation still owns native connection/GATT state that can poison the next attempt,
|
|
1606
|
-
* so it is cleared here without awaiting the same queue that stopped responding.
|
|
1607
|
-
*/
|
|
1608
|
-
private abandonStalledConnection(
|
|
1609
|
-
uuid: string,
|
|
1610
|
-
stage: 'connect-backstop' | 'connect-native' | 'gatt-backstop' | 'gatt-native'
|
|
1611
|
-
) {
|
|
1612
|
-
const timeouts = (this.connectionSetupTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1613
|
-
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1614
|
-
Log?.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1615
|
-
stage,
|
|
1616
|
-
setupTimeoutsSinceSuccess: timeouts,
|
|
1617
|
-
});
|
|
1618
|
-
|
|
1619
|
-
this.blePlxManager?.cancelDeviceConnection(uuid).catch(() => {
|
|
1620
|
-
// Rejects with "Operation was cancelled" while merely connecting — expected.
|
|
1621
|
-
});
|
|
1622
|
-
const stalled = transportCache[uuid];
|
|
1623
|
-
if (stalled) {
|
|
1624
|
-
delete transportCache[uuid];
|
|
1625
|
-
}
|
|
1626
|
-
this.deviceProtocol.delete(uuid);
|
|
1627
|
-
this.probingProtocols.delete(uuid);
|
|
1628
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
1629
|
-
this.resetProtocolV2Frames(uuid);
|
|
1630
|
-
|
|
1631
|
-
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1632
|
-
// BleManager.destroy() force-rejects every promise the native queue abandoned —
|
|
1633
|
-
// the only JS-reachable way to settle them — and drops all cached peripherals.
|
|
1634
|
-
Log?.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1635
|
-
this.resetPlxManager();
|
|
1636
|
-
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
1505
|
private getCachedTransport(uuid: string) {
|
|
1641
1506
|
const transport = transportCache[uuid];
|
|
1642
1507
|
if (!transport) {
|
|
@@ -1645,107 +1510,6 @@ export default class ReactNativeBleTransport {
|
|
|
1645
1510
|
return transport;
|
|
1646
1511
|
}
|
|
1647
1512
|
|
|
1648
|
-
/**
|
|
1649
|
-
* Write one packet under a bounded budget. A write that never settles means the
|
|
1650
|
-
* peripheral is wedged even though the GATT link still reports connected, so the
|
|
1651
|
-
* link is torn down: releasing JS state alone would leave the poisoned peripheral
|
|
1652
|
-
* cached and every later call would hang on it again.
|
|
1653
|
-
*/
|
|
1654
|
-
private async writeBlePacket(
|
|
1655
|
-
uuid: string,
|
|
1656
|
-
data: string,
|
|
1657
|
-
write: (payload: string) => Promise<unknown>,
|
|
1658
|
-
isCurrentOwner?: () => boolean
|
|
1659
|
-
) {
|
|
1660
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1661
|
-
let timedOut = false;
|
|
1662
|
-
try {
|
|
1663
|
-
await Promise.race([
|
|
1664
|
-
write(data),
|
|
1665
|
-
new Promise<never>((_, reject) => {
|
|
1666
|
-
timer = setTimeout(() => {
|
|
1667
|
-
timedOut = true;
|
|
1668
|
-
reject(
|
|
1669
|
-
ERRORS.TypedError(
|
|
1670
|
-
HardwareErrorCode.BleWriteCharacteristicError,
|
|
1671
|
-
`BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`
|
|
1672
|
-
)
|
|
1673
|
-
);
|
|
1674
|
-
}, BLE_WRITE_PACKET_TIMEOUT_MS);
|
|
1675
|
-
}),
|
|
1676
|
-
]);
|
|
1677
|
-
this.writeTimeoutCounts.delete(uuid);
|
|
1678
|
-
} catch (error) {
|
|
1679
|
-
if (timedOut) {
|
|
1680
|
-
// A superseded call's late write must not tear down the link the current
|
|
1681
|
-
// call is using; only the owner of the transport may declare it dead.
|
|
1682
|
-
if (isCurrentOwner && !isCurrentOwner()) {
|
|
1683
|
-
Log?.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
|
|
1684
|
-
} else {
|
|
1685
|
-
this.tearDownWedgedLink(uuid);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
throw error;
|
|
1689
|
-
} finally {
|
|
1690
|
-
if (timer) clearTimeout(timer);
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
/**
|
|
1695
|
-
* Drop a link whose writes stopped completing. The JS state is purged synchronously
|
|
1696
|
-
* so the next acquire() cannot reuse the dead transport, while the native teardown is
|
|
1697
|
-
* intentionally NOT awaited: it talks to the very layer that just stopped settling
|
|
1698
|
-
* promises, so awaiting it could hang exactly like the write it is recovering from.
|
|
1699
|
-
*/
|
|
1700
|
-
private tearDownWedgedLink(uuid: string) {
|
|
1701
|
-
const timeouts = (this.writeTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1702
|
-
this.writeTimeoutCounts.set(uuid, timeouts);
|
|
1703
|
-
Log?.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
|
|
1704
|
-
consecutiveWriteTimeouts: timeouts,
|
|
1705
|
-
});
|
|
1706
|
-
|
|
1707
|
-
const wedged = transportCache[uuid];
|
|
1708
|
-
this.disconnect(uuid).catch(error => {
|
|
1709
|
-
Log?.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
|
|
1710
|
-
});
|
|
1711
|
-
if (wedged && transportCache[uuid] === wedged) {
|
|
1712
|
-
delete transportCache[uuid];
|
|
1713
|
-
}
|
|
1714
|
-
this.deviceProtocol.delete(uuid);
|
|
1715
|
-
this.probingProtocols.delete(uuid);
|
|
1716
|
-
this.protocolV2Assemblers.delete(uuid);
|
|
1717
|
-
this.resetProtocolV2Frames(uuid);
|
|
1718
|
-
|
|
1719
|
-
if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1720
|
-
// Reconnecting reuses the same native peripheral object. When it stays wedged
|
|
1721
|
-
// across attempts the poison lives in the BLE manager itself, and only a fresh
|
|
1722
|
-
// manager drops every cached peripheral — the JS equivalent of restarting the app.
|
|
1723
|
-
Log?.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
|
|
1724
|
-
this.resetPlxManager();
|
|
1725
|
-
this.writeTimeoutCounts.delete(uuid);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
private resetPlxManager() {
|
|
1730
|
-
const manager = this.blePlxManager;
|
|
1731
|
-
this.blePlxManager = undefined;
|
|
1732
|
-
// Every cached transport belongs to the destroyed manager's peripherals.
|
|
1733
|
-
Object.keys(transportCache).forEach(key => {
|
|
1734
|
-
delete transportCache[key];
|
|
1735
|
-
});
|
|
1736
|
-
this.deviceProtocol.clear();
|
|
1737
|
-
this.probingProtocols.clear();
|
|
1738
|
-
this.sessionProtocols.clear();
|
|
1739
|
-
this.protocolReprobeFailures.clear();
|
|
1740
|
-
this.monitorTokens.clear();
|
|
1741
|
-
this.protocolV2Assemblers.clear();
|
|
1742
|
-
try {
|
|
1743
|
-
manager?.destroy();
|
|
1744
|
-
} catch (error) {
|
|
1745
|
-
Log?.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
1513
|
private createProtocolMismatchError(expected: ProtocolType) {
|
|
1750
1514
|
return ERRORS.TypedError(
|
|
1751
1515
|
HardwareErrorCode.RuntimeError,
|
|
@@ -1761,29 +1525,30 @@ export default class ReactNativeBleTransport {
|
|
|
1761
1525
|
}
|
|
1762
1526
|
|
|
1763
1527
|
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
1764
|
-
if (this.probingProtocols.get(uuid) === protocol) {
|
|
1765
|
-
this.probingProtocols.delete(uuid);
|
|
1766
|
-
}
|
|
1767
1528
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1768
1529
|
this.deviceProtocol.delete(uuid);
|
|
1769
1530
|
}
|
|
1770
1531
|
}
|
|
1771
1532
|
|
|
1772
|
-
/** Protocol to route a call with: confirmed if known, otherwise the one being probed. */
|
|
1773
|
-
private getActiveProtocol(uuid: string): ProtocolType | undefined {
|
|
1774
|
-
return this.deviceProtocol.get(uuid) ?? this.probingProtocols.get(uuid);
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
1533
|
private async detectProtocol(
|
|
1778
1534
|
uuid: string,
|
|
1779
1535
|
expectedProtocol?: ProtocolType,
|
|
1780
1536
|
protocolHint?: ProtocolType,
|
|
1781
1537
|
rebuildTransport?: () => Promise<void>
|
|
1782
1538
|
): Promise<ProtocolType> {
|
|
1539
|
+
if (Platform.OS === 'ios' && expectedProtocol) {
|
|
1540
|
+
this.deviceProtocol.set(uuid, expectedProtocol);
|
|
1541
|
+
Log?.debug('[ReactNativeBleTransport] protocol selected', {
|
|
1542
|
+
deviceId: uuid,
|
|
1543
|
+
protocol: expectedProtocol,
|
|
1544
|
+
source: 'expected',
|
|
1545
|
+
});
|
|
1546
|
+
return expectedProtocol;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1783
1549
|
if (expectedProtocol === 'V1') {
|
|
1784
1550
|
if (await this.probeProtocolV1(uuid)) {
|
|
1785
1551
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1786
|
-
this.sessionProtocols.set(uuid, 'V1');
|
|
1787
1552
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1788
1553
|
deviceId: uuid,
|
|
1789
1554
|
protocol: 'V1',
|
|
@@ -1797,7 +1562,6 @@ export default class ReactNativeBleTransport {
|
|
|
1797
1562
|
if (expectedProtocol === 'V2') {
|
|
1798
1563
|
if (await this.probeProtocolV2(uuid)) {
|
|
1799
1564
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1800
|
-
this.sessionProtocols.set(uuid, 'V2');
|
|
1801
1565
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1802
1566
|
deviceId: uuid,
|
|
1803
1567
|
protocol: 'V2',
|
|
@@ -1810,18 +1574,8 @@ export default class ReactNativeBleTransport {
|
|
|
1810
1574
|
|
|
1811
1575
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1812
1576
|
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1813
|
-
const
|
|
1814
|
-
const reprobeFailures = this.protocolReprobeFailures.get(uuid) ?? 0;
|
|
1815
|
-
const fullProbeOrder: ProtocolType[] =
|
|
1577
|
+
const probeOrder: ProtocolType[] =
|
|
1816
1578
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1817
|
-
// A device that already answered on a protocol in this session keeps answering on
|
|
1818
|
-
// it; while it is rebooting nothing answers at all, so probing the other protocol
|
|
1819
|
-
// only adds its timeout to every poll.
|
|
1820
|
-
const trustSessionProtocol =
|
|
1821
|
-
sessionProtocol !== undefined &&
|
|
1822
|
-
!protocolHint &&
|
|
1823
|
-
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
1824
|
-
const probeOrder: ProtocolType[] = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
1825
1579
|
|
|
1826
1580
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1827
1581
|
const protocol = probeOrder[i];
|
|
@@ -1839,8 +1593,6 @@ export default class ReactNativeBleTransport {
|
|
|
1839
1593
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1840
1594
|
if (detected) {
|
|
1841
1595
|
this.deviceProtocol.set(uuid, protocol);
|
|
1842
|
-
this.sessionProtocols.set(uuid, protocol);
|
|
1843
|
-
this.protocolReprobeFailures.delete(uuid);
|
|
1844
1596
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1845
1597
|
deviceId: uuid,
|
|
1846
1598
|
protocol,
|
|
@@ -1850,16 +1602,7 @@ export default class ReactNativeBleTransport {
|
|
|
1850
1602
|
}
|
|
1851
1603
|
}
|
|
1852
1604
|
|
|
1853
|
-
if (trustSessionProtocol) {
|
|
1854
|
-
// Still silent on its own protocol: count it, and let the streak expire the
|
|
1855
|
-
// shortcut so a device that genuinely switched protocols is found again.
|
|
1856
|
-
this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
|
|
1857
|
-
} else {
|
|
1858
|
-
this.protocolReprobeFailures.delete(uuid);
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
1605
|
this.deviceProtocol.delete(uuid);
|
|
1862
|
-
this.probingProtocols.delete(uuid);
|
|
1863
1606
|
throw this.createProtocolDetectionError();
|
|
1864
1607
|
}
|
|
1865
1608
|
|
|
@@ -1921,20 +1664,14 @@ export default class ReactNativeBleTransport {
|
|
|
1921
1664
|
}
|
|
1922
1665
|
|
|
1923
1666
|
try {
|
|
1924
|
-
this.
|
|
1667
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
1925
1668
|
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1926
1669
|
// session before Core has a chance to restore a hidden wallet.
|
|
1927
1670
|
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1928
|
-
this.probingProtocols.delete(uuid);
|
|
1929
1671
|
return true;
|
|
1930
1672
|
} catch (error) {
|
|
1931
1673
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1932
1674
|
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1933
|
-
// A wedged write already dropped the link, so probing another protocol on it
|
|
1934
|
-
// would only fail against a torn-down transport: surface the real cause.
|
|
1935
|
-
if (isWedgedWriteError(error)) {
|
|
1936
|
-
throw error;
|
|
1937
|
-
}
|
|
1938
1675
|
return false;
|
|
1939
1676
|
}
|
|
1940
1677
|
}
|
|
@@ -1944,7 +1681,7 @@ export default class ReactNativeBleTransport {
|
|
|
1944
1681
|
return false;
|
|
1945
1682
|
}
|
|
1946
1683
|
|
|
1947
|
-
this.
|
|
1684
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1948
1685
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1949
1686
|
const detected = await probeProtocolV2Helper({
|
|
1950
1687
|
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
@@ -1959,8 +1696,6 @@ export default class ReactNativeBleTransport {
|
|
|
1959
1696
|
});
|
|
1960
1697
|
if (!detected) {
|
|
1961
1698
|
this.clearProbeProtocol(uuid, 'V2');
|
|
1962
|
-
} else {
|
|
1963
|
-
this.probingProtocols.delete(uuid);
|
|
1964
1699
|
}
|
|
1965
1700
|
return detected;
|
|
1966
1701
|
}
|
|
@@ -2042,12 +1777,17 @@ export default class ReactNativeBleTransport {
|
|
|
2042
1777
|
}
|
|
2043
1778
|
|
|
2044
1779
|
private async writeProtocolV2Packet(
|
|
2045
|
-
uuid: string,
|
|
2046
1780
|
transport: BleTransport,
|
|
2047
1781
|
base64: string,
|
|
2048
1782
|
context: ProtocolV2CallContext,
|
|
2049
1783
|
assertCurrentGeneration: () => void
|
|
2050
1784
|
) {
|
|
1785
|
+
const shouldUseWriteWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1786
|
+
platform: Platform.OS,
|
|
1787
|
+
highVolume: context.highVolume,
|
|
1788
|
+
requestedWithResponse: context.writeWithResponse,
|
|
1789
|
+
characteristic: transport.writeCharacteristic,
|
|
1790
|
+
});
|
|
2051
1791
|
let attempt = 0;
|
|
2052
1792
|
for (;;) {
|
|
2053
1793
|
assertCurrentGeneration();
|
|
@@ -2055,21 +1795,11 @@ export default class ReactNativeBleTransport {
|
|
|
2055
1795
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
2056
1796
|
}
|
|
2057
1797
|
try {
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
// tear down the link that the current generation is using.
|
|
2064
|
-
() => {
|
|
2065
|
-
try {
|
|
2066
|
-
assertCurrentGeneration();
|
|
2067
|
-
return !context.signal.aborted;
|
|
2068
|
-
} catch {
|
|
2069
|
-
return false;
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
);
|
|
1798
|
+
if (shouldUseWriteWithResponse) {
|
|
1799
|
+
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1800
|
+
} else {
|
|
1801
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1802
|
+
}
|
|
2073
1803
|
assertCurrentGeneration();
|
|
2074
1804
|
return;
|
|
2075
1805
|
} catch (error) {
|
|
@@ -2092,7 +1822,6 @@ export default class ReactNativeBleTransport {
|
|
|
2092
1822
|
}
|
|
2093
1823
|
|
|
2094
1824
|
private async writeProtocolV2Frame(
|
|
2095
|
-
uuid: string,
|
|
2096
1825
|
transport: BleTransport,
|
|
2097
1826
|
frame: Uint8Array,
|
|
2098
1827
|
context: ProtocolV2CallContext,
|
|
@@ -2103,7 +1832,7 @@ export default class ReactNativeBleTransport {
|
|
|
2103
1832
|
platform: Platform.OS,
|
|
2104
1833
|
iosPacketLength: tuning.iosPacketLength,
|
|
2105
1834
|
androidPacketLength: tuning.androidPacketLength,
|
|
2106
|
-
mtu:
|
|
1835
|
+
mtu: transport.mtuSize,
|
|
2107
1836
|
});
|
|
2108
1837
|
await writeProtocolV2BleFrame({
|
|
2109
1838
|
frame,
|
|
@@ -2111,13 +1840,9 @@ export default class ReactNativeBleTransport {
|
|
|
2111
1840
|
assertActive: assertCurrentGeneration,
|
|
2112
1841
|
signal: context.signal,
|
|
2113
1842
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
2114
|
-
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
2115
|
-
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
2116
|
-
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
2117
1843
|
wait: delay,
|
|
2118
1844
|
writePacket: packet =>
|
|
2119
1845
|
this.writeProtocolV2Packet(
|
|
2120
|
-
uuid,
|
|
2121
1846
|
transport,
|
|
2122
1847
|
Buffer.from(packet).toString('base64'),
|
|
2123
1848
|
context,
|
|
@@ -2141,11 +1866,39 @@ export default class ReactNativeBleTransport {
|
|
|
2141
1866
|
|
|
2142
1867
|
if (highVolumeWrite) {
|
|
2143
1868
|
const tuning = getProtocolV2BleTuning();
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
1869
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1870
|
+
const writeWithResponse = shouldWriteProtocolV2WithResponse({
|
|
1871
|
+
platform: Platform.OS,
|
|
1872
|
+
highVolume: true,
|
|
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,
|
|
2148
1881
|
});
|
|
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);
|
|
2149
1902
|
}
|
|
2150
1903
|
|
|
2151
1904
|
try {
|
|
@@ -2159,6 +1912,73 @@ export default class ReactNativeBleTransport {
|
|
|
2159
1912
|
} catch (e) {
|
|
2160
1913
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
2161
1914
|
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
|
+
});
|
|
2162
1982
|
}
|
|
2163
1983
|
}
|
|
2164
1984
|
|
|
@@ -2182,13 +2002,7 @@ export default class ReactNativeBleTransport {
|
|
|
2182
2002
|
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
2183
2003
|
assertCurrentGeneration();
|
|
2184
2004
|
const currentTransport = this.getCachedTransport(uuid);
|
|
2185
|
-
await this.writeProtocolV2Frame(
|
|
2186
|
-
uuid,
|
|
2187
|
-
currentTransport,
|
|
2188
|
-
frame,
|
|
2189
|
-
context,
|
|
2190
|
-
assertCurrentGeneration
|
|
2191
|
-
);
|
|
2005
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
2192
2006
|
},
|
|
2193
2007
|
readFrame: async () => {
|
|
2194
2008
|
assertCurrentGeneration();
|
|
@@ -2213,6 +2027,6 @@ export default class ReactNativeBleTransport {
|
|
|
2213
2027
|
}
|
|
2214
2028
|
|
|
2215
2029
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
2216
|
-
return this.
|
|
2030
|
+
return this.deviceProtocol.get(path);
|
|
2217
2031
|
}
|
|
2218
2032
|
}
|