@onekeyfe/hd-transport-react-native 1.2.2-alpha.104 → 1.2.2-alpha.106
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/README.md +1 -1
- package/dist/bleNativeDisconnect.d.ts +3 -0
- package/dist/bleNativeDisconnect.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -50
- package/package.json +5 -5
- package/src/__tests__/bleNativeDisconnect.test.ts +33 -0
- package/src/__tests__/connectTimeout.test.ts +110 -2
- package/src/__tests__/protocolV2Link.test.ts +112 -2
- package/src/__tests__/staleCallTimeout.test.ts +34 -5
- package/src/bleNativeDisconnect.ts +40 -0
- package/src/index.ts +187 -54
package/src/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
getInfosForServiceUuid,
|
|
48
48
|
isSameBleUuid,
|
|
49
49
|
} from './constants';
|
|
50
|
+
import { isNativeBleDisconnectError, toBleDisconnectHardwareError } from './bleNativeDisconnect';
|
|
50
51
|
import {
|
|
51
52
|
isBleStaleBondHardwareError,
|
|
52
53
|
isNativeBleStaleBondError,
|
|
@@ -76,6 +77,8 @@ const { check, ProtocolV1, parseConfigure } = transport;
|
|
|
76
77
|
const Log = bleLogger;
|
|
77
78
|
|
|
78
79
|
const transportCache: Record<string, BleTransport> = {};
|
|
80
|
+
// ble-plx shares one manager across transport instances in this JS runtime.
|
|
81
|
+
let bleManagerResetPromise: Promise<void> | undefined;
|
|
79
82
|
const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
80
83
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
81
84
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
@@ -168,6 +171,23 @@ const isWedgedWriteError = (error: unknown): boolean =>
|
|
|
168
171
|
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleWriteCharacteristicError &&
|
|
169
172
|
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
170
173
|
(error as { message: string }).message.startsWith(WEDGED_WRITE_MESSAGE);
|
|
174
|
+
const shouldRethrowProtocolProbeError = (error: unknown): boolean => {
|
|
175
|
+
const code = (error as { errorCode?: unknown })?.errorCode;
|
|
176
|
+
// Bonding and GATT failures are not evidence of a protocol mismatch. Preserve
|
|
177
|
+
// them instead of probing another protocol on an unusable connection.
|
|
178
|
+
// Native PLX disconnects (errorCode 201 / iOS 7) must match before they are
|
|
179
|
+
// mapped: Protocol V2 writes rethrow them unchanged unless normalized first.
|
|
180
|
+
return (
|
|
181
|
+
isBleStaleBondHardwareError(error) ||
|
|
182
|
+
isNativeBleDisconnectError(error) ||
|
|
183
|
+
code === HardwareErrorCode.BleDeviceNotBonded ||
|
|
184
|
+
code === HardwareErrorCode.BleDeviceBondedCanceled ||
|
|
185
|
+
code === HardwareErrorCode.BleDeviceDisconnected ||
|
|
186
|
+
code === HardwareErrorCode.BleCharacteristicNotifyError ||
|
|
187
|
+
code === HardwareErrorCode.BleCharacteristicNotifyChangeFailure ||
|
|
188
|
+
code === HardwareErrorCode.BleWriteCharacteristicError
|
|
189
|
+
);
|
|
190
|
+
};
|
|
171
191
|
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
172
192
|
export const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
173
193
|
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
@@ -450,6 +470,10 @@ export default class ReactNativeBleTransport {
|
|
|
450
470
|
/** Serializes transport lifecycle changes for the same physical device. */
|
|
451
471
|
private lifecycleOperations: Map<string, Promise<void>> = new Map();
|
|
452
472
|
|
|
473
|
+
private stopPromise?: Promise<void>;
|
|
474
|
+
|
|
475
|
+
private scanCleanups = new Set<() => Promise<void>>();
|
|
476
|
+
|
|
453
477
|
constructor(options: TransportOptions) {
|
|
454
478
|
this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
|
|
455
479
|
}
|
|
@@ -485,10 +509,34 @@ export default class ReactNativeBleTransport {
|
|
|
485
509
|
// empty
|
|
486
510
|
}
|
|
487
511
|
|
|
488
|
-
getPlxManager(): Promise<BlePlxManager> {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
512
|
+
async getPlxManager(): Promise<BlePlxManager> {
|
|
513
|
+
while (bleManagerResetPromise) {
|
|
514
|
+
await this.waitForManagerReset();
|
|
515
|
+
}
|
|
516
|
+
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
517
|
+
if (!this.blePlxManager) this.blePlxManager = new BlePlxManager();
|
|
518
|
+
return this.blePlxManager;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private async waitForManagerReset(): Promise<void> {
|
|
522
|
+
if (!bleManagerResetPromise) return;
|
|
523
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
524
|
+
try {
|
|
525
|
+
await Promise.race([
|
|
526
|
+
bleManagerResetPromise,
|
|
527
|
+
new Promise<never>((_, reject) => {
|
|
528
|
+
timeout = setTimeout(
|
|
529
|
+
() => reject(this.createWedgedBleSetupError()),
|
|
530
|
+
BLE_CONNECT_TIMEOUT_MS
|
|
531
|
+
);
|
|
532
|
+
}),
|
|
533
|
+
]);
|
|
534
|
+
} catch {
|
|
535
|
+
// A timeout or failed destroy is not permission to reuse the old singleton.
|
|
536
|
+
throw this.createWedgedBleSetupError();
|
|
537
|
+
} finally {
|
|
538
|
+
if (timeout) clearTimeout(timeout);
|
|
539
|
+
}
|
|
492
540
|
}
|
|
493
541
|
|
|
494
542
|
async resolveCharacteristics(device: Device): Promise<ResolvedBleCharacteristics> {
|
|
@@ -677,35 +725,58 @@ export default class ReactNativeBleTransport {
|
|
|
677
725
|
* @returns
|
|
678
726
|
*/
|
|
679
727
|
async enumerate() {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
} catch (error) {
|
|
687
|
-
Log?.debug('subscribeBleOn error: ', error);
|
|
688
|
-
reject(error);
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
if (Platform.OS === 'android' && Platform.Version >= 31) {
|
|
693
|
-
Log?.debug('requesting permissions, please wait...');
|
|
728
|
+
const scanStartedAt = Date.now();
|
|
729
|
+
let firstDeviceMs: number | undefined;
|
|
730
|
+
const blePlxManager = await this.getPlxManager();
|
|
731
|
+
await subscribeBleOn(blePlxManager);
|
|
732
|
+
if (Platform.OS === 'android' && Platform.Version >= 31) {
|
|
733
|
+
Log?.debug('requesting permissions, please wait...');
|
|
694
734
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
735
|
+
const resultConnect = await PermissionsAndroid.requestMultiple([
|
|
736
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
|
|
737
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
|
|
738
|
+
]);
|
|
699
739
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
740
|
+
Log?.debug('requesting permissions, result: ', resultConnect);
|
|
741
|
+
if (
|
|
742
|
+
resultConnect[PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
|
|
743
|
+
resultConnect[PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted'
|
|
744
|
+
) {
|
|
745
|
+
throw ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
|
|
708
746
|
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
750
|
+
return new Promise<IOneKeyDevice[]>((resolve, reject) => {
|
|
751
|
+
const deviceList: IOneKeyDevice[] = [];
|
|
752
|
+
let finished = false;
|
|
753
|
+
let scanCleanup: Promise<void> | undefined;
|
|
754
|
+
const finishScan = (error?: unknown) => {
|
|
755
|
+
if (scanCleanup) return scanCleanup;
|
|
756
|
+
finished = true;
|
|
757
|
+
clearScanTimer();
|
|
758
|
+
scanCleanup = this.runNativeTeardown('scan', blePlxManager, async () => {
|
|
759
|
+
await blePlxManager.stopDeviceScan();
|
|
760
|
+
}).then(() => {
|
|
761
|
+
this.scanCleanups.delete(cancelScan);
|
|
762
|
+
if (error) reject(error);
|
|
763
|
+
else resolve(deviceList);
|
|
764
|
+
});
|
|
765
|
+
return scanCleanup;
|
|
766
|
+
};
|
|
767
|
+
const cancelScan = () =>
|
|
768
|
+
finishScan(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
769
|
+
this.scanCleanups.add(cancelScan);
|
|
770
|
+
|
|
771
|
+
const clearScanTimer = timer.timeout(() => {
|
|
772
|
+
Log?.debug('[ReactNativeBleTransport] scan completed', {
|
|
773
|
+
elapsedMs: Date.now() - scanStartedAt,
|
|
774
|
+
firstDeviceMs,
|
|
775
|
+
deviceCount: deviceList.length,
|
|
776
|
+
scanWindowMs: this.scanTimeout,
|
|
777
|
+
});
|
|
778
|
+
finishScan();
|
|
779
|
+
}, this.scanTimeout);
|
|
709
780
|
|
|
710
781
|
blePlxManager.startDeviceScan(
|
|
711
782
|
getBluetoothServiceUuids(),
|
|
@@ -721,18 +792,17 @@ export default class ReactNativeBleTransport {
|
|
|
721
792
|
error.errorCode
|
|
722
793
|
)
|
|
723
794
|
) {
|
|
724
|
-
|
|
795
|
+
finishScan(ERRORS.TypedError(HardwareErrorCode.BlePermissionError));
|
|
725
796
|
} else if (error.errorCode === BleErrorCode.BluetoothUnauthorized) {
|
|
726
|
-
|
|
797
|
+
finishScan(ERRORS.TypedError(HardwareErrorCode.BleLocationError));
|
|
727
798
|
} else if (error.errorCode === BleErrorCode.LocationServicesDisabled) {
|
|
728
|
-
|
|
799
|
+
finishScan(ERRORS.TypedError(HardwareErrorCode.BleLocationServicesDisabled));
|
|
729
800
|
} else if (error.errorCode === BleErrorCode.ScanStartFailed) {
|
|
730
801
|
// Android Bluetooth will report an error when the search frequency is too fast,
|
|
731
802
|
// then nothing is processed and an empty array of devices is returned.
|
|
732
803
|
// Then the next search will be back to normal
|
|
733
|
-
timer.timeout(() => {}, this.scanTimeout);
|
|
734
804
|
} else {
|
|
735
|
-
|
|
805
|
+
finishScan(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.reason ?? ''));
|
|
736
806
|
}
|
|
737
807
|
return;
|
|
738
808
|
}
|
|
@@ -764,6 +834,7 @@ export default class ReactNativeBleTransport {
|
|
|
764
834
|
}
|
|
765
835
|
);
|
|
766
836
|
|
|
837
|
+
if (finished) return;
|
|
767
838
|
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
768
839
|
devices => {
|
|
769
840
|
for (const device of devices) {
|
|
@@ -783,11 +854,13 @@ export default class ReactNativeBleTransport {
|
|
|
783
854
|
addDevice(device as unknown as Device);
|
|
784
855
|
}
|
|
785
856
|
}
|
|
786
|
-
}
|
|
857
|
+
},
|
|
858
|
+
error => Log?.debug('search connected peripheral failed:', error)
|
|
787
859
|
);
|
|
788
860
|
|
|
789
861
|
const addDevice = (device: Device) => {
|
|
790
|
-
if (deviceList.every(d => d.id !== device.id)) {
|
|
862
|
+
if (!finished && deviceList.every(d => d.id !== device.id)) {
|
|
863
|
+
firstDeviceMs ??= Date.now() - scanStartedAt;
|
|
791
864
|
const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
|
|
792
865
|
|
|
793
866
|
deviceList.push({
|
|
@@ -802,11 +875,6 @@ export default class ReactNativeBleTransport {
|
|
|
802
875
|
});
|
|
803
876
|
}
|
|
804
877
|
};
|
|
805
|
-
|
|
806
|
-
timer.timeout(() => {
|
|
807
|
-
blePlxManager.stopDeviceScan();
|
|
808
|
-
resolve(deviceList);
|
|
809
|
-
}, this.scanTimeout);
|
|
810
878
|
});
|
|
811
879
|
}
|
|
812
880
|
|
|
@@ -894,6 +962,7 @@ export default class ReactNativeBleTransport {
|
|
|
894
962
|
}
|
|
895
963
|
|
|
896
964
|
private async acquireUnlocked(input: FirmwareInstallBleAcquireInput) {
|
|
965
|
+
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
897
966
|
const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
|
|
898
967
|
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
899
968
|
? expectedProtocol === 'V2'
|
|
@@ -1170,7 +1239,7 @@ export default class ReactNativeBleTransport {
|
|
|
1170
1239
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1171
1240
|
return { uuid, protocolType };
|
|
1172
1241
|
} catch (error) {
|
|
1173
|
-
if (isBleStaleBondHardwareError(error)) {
|
|
1242
|
+
if (isBleStaleBondHardwareError(error) || shouldRethrowProtocolProbeError(error)) {
|
|
1174
1243
|
await this.disconnectUnlocked(uuid);
|
|
1175
1244
|
} else {
|
|
1176
1245
|
await this.releaseUnlocked(uuid, true);
|
|
@@ -1703,7 +1772,34 @@ export default class ReactNativeBleTransport {
|
|
|
1703
1772
|
}
|
|
1704
1773
|
|
|
1705
1774
|
stop() {
|
|
1775
|
+
if (this.stopPromise) return this.stopPromise;
|
|
1706
1776
|
this.stopped = true;
|
|
1777
|
+
const deviceIds = new Set([
|
|
1778
|
+
...this.monitorTokens.keys(),
|
|
1779
|
+
...this.sessionProtocols.keys(),
|
|
1780
|
+
...this.lifecycleOperations.keys(),
|
|
1781
|
+
...(this.runPromiseDeviceId ? [this.runPromiseDeviceId] : []),
|
|
1782
|
+
]);
|
|
1783
|
+
const scans = Array.from(this.scanCleanups, cleanup => cleanup());
|
|
1784
|
+
this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
|
|
1785
|
+
this.androidPriorityResetTimers.clear();
|
|
1786
|
+
this.androidHighPriorityDevices.clear();
|
|
1787
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1788
|
+
this.runPromise?.reject(error);
|
|
1789
|
+
this.runPromise = null;
|
|
1790
|
+
this.runPromiseDeviceId = null;
|
|
1791
|
+
deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
|
|
1792
|
+
// Release only this transport's endpoints; other connectors may share ble-plx.
|
|
1793
|
+
this.stopPromise = Promise.all([
|
|
1794
|
+
...scans,
|
|
1795
|
+
...Array.from(deviceIds, uuid => this.disconnect(uuid)),
|
|
1796
|
+
]).then(async () => {
|
|
1797
|
+
await this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
|
|
1798
|
+
await this.waitForManagerReset();
|
|
1799
|
+
this.blePlxManager = undefined;
|
|
1800
|
+
this.emitter = undefined;
|
|
1801
|
+
});
|
|
1802
|
+
return this.stopPromise;
|
|
1707
1803
|
}
|
|
1708
1804
|
|
|
1709
1805
|
async disconnect(session: string) {
|
|
@@ -1855,17 +1951,26 @@ export default class ReactNativeBleTransport {
|
|
|
1855
1951
|
}
|
|
1856
1952
|
}
|
|
1857
1953
|
|
|
1858
|
-
cancel() {
|
|
1954
|
+
async cancel() {
|
|
1859
1955
|
Log?.debug('transport-react-native transport cancel');
|
|
1860
|
-
|
|
1861
|
-
|
|
1956
|
+
const pending = this.runPromise;
|
|
1957
|
+
const deviceId = this.runPromiseDeviceId;
|
|
1958
|
+
if (pending) {
|
|
1959
|
+
pending.reject(ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled));
|
|
1960
|
+
if (this.runPromise === pending) {
|
|
1961
|
+
this.runPromise = null;
|
|
1962
|
+
this.runPromiseDeviceId = null;
|
|
1963
|
+
}
|
|
1964
|
+
// A V1 read cannot be safely reused after abandoning its response.
|
|
1965
|
+
// Drain native teardown before DeviceCommands releases the operation.
|
|
1966
|
+
if (deviceId) await this.disconnect(deviceId);
|
|
1862
1967
|
}
|
|
1863
|
-
this.runPromise = null;
|
|
1864
|
-
this.runPromiseDeviceId = null;
|
|
1865
1968
|
}
|
|
1866
1969
|
|
|
1867
1970
|
/** Run a native connect under the JS backstop budget. */
|
|
1868
1971
|
private async connectWithTimeout<T>(uuid: string, connect: () => Promise<T>): Promise<T> {
|
|
1972
|
+
const startedAt = Date.now();
|
|
1973
|
+
let succeeded = false;
|
|
1869
1974
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1870
1975
|
let timedOut = false;
|
|
1871
1976
|
const pending = connect();
|
|
@@ -1887,6 +1992,7 @@ export default class ReactNativeBleTransport {
|
|
|
1887
1992
|
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1888
1993
|
}),
|
|
1889
1994
|
]);
|
|
1995
|
+
succeeded = true;
|
|
1890
1996
|
return result;
|
|
1891
1997
|
} catch (error) {
|
|
1892
1998
|
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
@@ -1901,6 +2007,12 @@ export default class ReactNativeBleTransport {
|
|
|
1901
2007
|
throw error;
|
|
1902
2008
|
} finally {
|
|
1903
2009
|
if (timer) clearTimeout(timer);
|
|
2010
|
+
Log?.debug('[ReactNativeBleTransport] connect completed', {
|
|
2011
|
+
connectIdSuffix: uuid.slice(-8),
|
|
2012
|
+
elapsedMs: Date.now() - startedAt,
|
|
2013
|
+
succeeded,
|
|
2014
|
+
backstopExpired: timedOut,
|
|
2015
|
+
});
|
|
1904
2016
|
}
|
|
1905
2017
|
}
|
|
1906
2018
|
|
|
@@ -1909,6 +2021,8 @@ export default class ReactNativeBleTransport {
|
|
|
1909
2021
|
uuid: string,
|
|
1910
2022
|
device: Device
|
|
1911
2023
|
): Promise<ResolvedBleCharacteristics> {
|
|
2024
|
+
const startedAt = Date.now();
|
|
2025
|
+
let succeeded = false;
|
|
1912
2026
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1913
2027
|
let timedOut = false;
|
|
1914
2028
|
const pending = this.resolveCharacteristics(device);
|
|
@@ -1929,6 +2043,7 @@ export default class ReactNativeBleTransport {
|
|
|
1929
2043
|
}),
|
|
1930
2044
|
]);
|
|
1931
2045
|
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
2046
|
+
succeeded = true;
|
|
1932
2047
|
return result;
|
|
1933
2048
|
} catch (error) {
|
|
1934
2049
|
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
@@ -1943,6 +2058,12 @@ export default class ReactNativeBleTransport {
|
|
|
1943
2058
|
throw error;
|
|
1944
2059
|
} finally {
|
|
1945
2060
|
if (timer) clearTimeout(timer);
|
|
2061
|
+
Log?.debug('[ReactNativeBleTransport] GATT setup completed', {
|
|
2062
|
+
connectIdSuffix: uuid.slice(-8),
|
|
2063
|
+
elapsedMs: Date.now() - startedAt,
|
|
2064
|
+
succeeded,
|
|
2065
|
+
backstopExpired: timedOut,
|
|
2066
|
+
});
|
|
1946
2067
|
}
|
|
1947
2068
|
}
|
|
1948
2069
|
|
|
@@ -2086,6 +2207,7 @@ export default class ReactNativeBleTransport {
|
|
|
2086
2207
|
}
|
|
2087
2208
|
|
|
2088
2209
|
private resetPlxManager() {
|
|
2210
|
+
if (bleManagerResetPromise) return;
|
|
2089
2211
|
const manager = this.blePlxManager;
|
|
2090
2212
|
this.blePlxManager = undefined;
|
|
2091
2213
|
const reason = 'React Native BLE manager reset';
|
|
@@ -2132,11 +2254,21 @@ export default class ReactNativeBleTransport {
|
|
|
2132
2254
|
this.connectionSetupTimeoutCounts.clear();
|
|
2133
2255
|
this.monitorTokens.clear();
|
|
2134
2256
|
this.protocolV2Assemblers.clear();
|
|
2257
|
+
let reset: Promise<void>;
|
|
2135
2258
|
try {
|
|
2136
|
-
manager?.destroy();
|
|
2259
|
+
reset = Promise.resolve(manager?.destroy());
|
|
2137
2260
|
} catch (error) {
|
|
2138
|
-
|
|
2261
|
+
reset = Promise.reject(error);
|
|
2139
2262
|
}
|
|
2263
|
+
bleManagerResetPromise = reset;
|
|
2264
|
+
reset.then(
|
|
2265
|
+
() => {
|
|
2266
|
+
if (bleManagerResetPromise === reset) bleManagerResetPromise = undefined;
|
|
2267
|
+
},
|
|
2268
|
+
error => {
|
|
2269
|
+
Log?.error('[ReactNativeBleTransport] BLE manager destroy failed:', error);
|
|
2270
|
+
}
|
|
2271
|
+
);
|
|
2140
2272
|
}
|
|
2141
2273
|
|
|
2142
2274
|
private createProtocolMismatchError(expected: ProtocolType) {
|
|
@@ -2339,9 +2471,7 @@ export default class ReactNativeBleTransport {
|
|
|
2339
2471
|
} catch (error) {
|
|
2340
2472
|
this.clearProbeProtocol(uuid, 'V1');
|
|
2341
2473
|
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
2342
|
-
|
|
2343
|
-
// would only fail against a torn-down transport: surface the real cause.
|
|
2344
|
-
if (isWedgedWriteError(error)) {
|
|
2474
|
+
if (shouldRethrowProtocolProbeError(error)) {
|
|
2345
2475
|
throw error;
|
|
2346
2476
|
}
|
|
2347
2477
|
return false;
|
|
@@ -2366,7 +2496,7 @@ export default class ReactNativeBleTransport {
|
|
|
2366
2496
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2367
2497
|
this.resetProtocolV2Frames(uuid);
|
|
2368
2498
|
},
|
|
2369
|
-
shouldRethrow:
|
|
2499
|
+
shouldRethrow: shouldRethrowProtocolProbeError,
|
|
2370
2500
|
});
|
|
2371
2501
|
if (!detected) {
|
|
2372
2502
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -2513,6 +2643,9 @@ export default class ReactNativeBleTransport {
|
|
|
2513
2643
|
this.rememberStaleBondError(uuid, bondError);
|
|
2514
2644
|
throw bondError;
|
|
2515
2645
|
}
|
|
2646
|
+
if (isNativeBleDisconnectError(error)) {
|
|
2647
|
+
throw toBleDisconnectHardwareError(error);
|
|
2648
|
+
}
|
|
2516
2649
|
if (
|
|
2517
2650
|
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
2518
2651
|
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|