@onekeyfe/hd-transport-react-native 1.2.3-alpha.10 → 1.2.3-alpha.11
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/BleManager.d.ts +5 -6
- package/dist/BleManager.d.ts.map +1 -1
- package/dist/index.d.ts +10 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +151 -396
- package/package.json +6 -6
- package/src/BleManager.ts +38 -78
- package/src/__tests__/enumerate.test.ts +0 -15
- package/src/__tests__/protocolReprobe.test.ts +93 -1
- package/src/__tests__/protocolV2Link.test.ts +0 -15
- package/src/index.ts +178 -162
- package/dist/bleEncryption.d.ts +0 -17
- package/dist/bleEncryption.d.ts.map +0 -1
- package/dist/bleKeyMissing.d.ts +0 -7
- package/dist/bleKeyMissing.d.ts.map +0 -1
- package/src/__tests__/androidEncryption.test.ts +0 -482
- package/src/__tests__/androidKeyMissing.test.ts +0 -307
- package/src/bleEncryption.ts +0 -267
- package/src/bleKeyMissing.ts +0 -106
package/src/index.ts
CHANGED
|
@@ -47,18 +47,6 @@ import {
|
|
|
47
47
|
getInfosForServiceUuid,
|
|
48
48
|
isSameBleUuid,
|
|
49
49
|
} from './constants';
|
|
50
|
-
import {
|
|
51
|
-
isBleKeyMissingSupported,
|
|
52
|
-
startBleKeyMissingTracking,
|
|
53
|
-
stopBleKeyMissingTracking,
|
|
54
|
-
waitForBleKeyMissing,
|
|
55
|
-
} from './bleKeyMissing';
|
|
56
|
-
import {
|
|
57
|
-
markBleLinkEncrypted,
|
|
58
|
-
startBleEncryptionTracking,
|
|
59
|
-
stopBleEncryptionTracking,
|
|
60
|
-
waitForAndroidLinkEncryption,
|
|
61
|
-
} from './bleEncryption';
|
|
62
50
|
import { isNativeBleDisconnectError, toBleDisconnectHardwareError } from './bleNativeDisconnect';
|
|
63
51
|
import {
|
|
64
52
|
isBleStaleBondHardwareError,
|
|
@@ -74,7 +62,6 @@ import type { Deferred } from '@onekeyfe/hd-shared';
|
|
|
74
62
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
75
63
|
import type EventEmitter from 'events';
|
|
76
64
|
import type { BleAcquireInput, TransportOptions } from './types';
|
|
77
|
-
import type { AndroidLinkEncryption } from './bleEncryption';
|
|
78
65
|
|
|
79
66
|
type FirmwareInstallBleAcquireInput = BleAcquireInput & {
|
|
80
67
|
/**
|
|
@@ -201,36 +188,50 @@ const shouldRethrowProtocolProbeError = (error: unknown): boolean => {
|
|
|
201
188
|
code === HardwareErrorCode.BleWriteCharacteristicError
|
|
202
189
|
);
|
|
203
190
|
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
191
|
+
|
|
192
|
+
const logIosBleBondDiagnostic = (
|
|
193
|
+
phase: string,
|
|
194
|
+
error: unknown,
|
|
195
|
+
outcome: 'fallback' | 'rethrow'
|
|
196
|
+
) => {
|
|
197
|
+
if (Platform.OS !== 'ios') return;
|
|
198
|
+
|
|
199
|
+
const nativeError = (error ?? {}) as {
|
|
200
|
+
name?: unknown;
|
|
201
|
+
errorCode?: unknown;
|
|
202
|
+
attErrorCode?: unknown;
|
|
203
|
+
iosErrorCode?: unknown;
|
|
204
|
+
androidErrorCode?: unknown;
|
|
205
|
+
};
|
|
206
|
+
const scalar = (value: unknown) =>
|
|
207
|
+
typeof value === 'string' || typeof value === 'number' ? value : undefined;
|
|
208
|
+
|
|
209
|
+
Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe error', {
|
|
210
|
+
phase,
|
|
211
|
+
outcome,
|
|
212
|
+
errorName: scalar(nativeError.name),
|
|
213
|
+
errorCode: scalar(nativeError.errorCode),
|
|
214
|
+
attErrorCode: scalar(nativeError.attErrorCode),
|
|
215
|
+
iosErrorCode: scalar(nativeError.iosErrorCode),
|
|
216
|
+
androidErrorCode: scalar(nativeError.androidErrorCode),
|
|
217
|
+
nativeStaleBond: isNativeBleStaleBondError(error),
|
|
218
|
+
hardwareStaleBond: isBleStaleBondHardwareError(error),
|
|
219
|
+
nativeDisconnect: isNativeBleDisconnectError(error),
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
type IosProbeDisconnectEvidence = {
|
|
224
|
+
phase: 'protocol-v1-get-features' | 'protocol-v2-ping';
|
|
225
|
+
errorCode?: string | number;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const isIosProbeDisconnectError = (error: unknown): boolean => {
|
|
229
|
+
if (Platform.OS !== 'ios') return false;
|
|
227
230
|
const code = (error as { errorCode?: unknown })?.errorCode;
|
|
228
231
|
return (
|
|
229
232
|
isNativeBleDisconnectError(error) ||
|
|
230
233
|
code === HardwareErrorCode.BleDeviceNotBonded ||
|
|
231
|
-
code === HardwareErrorCode.BleDeviceDisconnected
|
|
232
|
-
code === HardwareErrorCode.BleCharacteristicNotifyError ||
|
|
233
|
-
code === HardwareErrorCode.BleConnectedError
|
|
234
|
+
code === HardwareErrorCode.BleDeviceDisconnected
|
|
234
235
|
);
|
|
235
236
|
};
|
|
236
237
|
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
@@ -575,6 +576,14 @@ export default class ReactNativeBleTransport {
|
|
|
575
576
|
/** Endpoints whose last detection got no answer; 'woken' once their Initialize wake is spent. */
|
|
576
577
|
private silentDetections = new Map<string, 'silent' | 'woken'>();
|
|
577
578
|
|
|
579
|
+
/**
|
|
580
|
+
* iOS can report a reset Pro2 as a generic peripheral disconnect instead of
|
|
581
|
+
* CBErrorPeerRemovedPairingInformation. Keep that first probe failure across
|
|
582
|
+
* the automatic cold reconnect and require a second failed probe before
|
|
583
|
+
* classifying the OS bond as invalid.
|
|
584
|
+
*/
|
|
585
|
+
private iosProbeDisconnectEvidence = new Map<string, IosProbeDisconnectEvidence>();
|
|
586
|
+
|
|
578
587
|
/** Android endpoints whose cached GATT table is suspect; the next connect refreshes it. */
|
|
579
588
|
private androidGattCacheRefreshes = new Set<string>();
|
|
580
589
|
|
|
@@ -585,9 +594,6 @@ export default class ReactNativeBleTransport {
|
|
|
585
594
|
*/
|
|
586
595
|
private staleBondErrors: Map<string, Error> = new Map();
|
|
587
596
|
|
|
588
|
-
/** When the current Android link attempt began; bounds which key-missing signals apply. */
|
|
589
|
-
private androidLinkStartedAt: Map<string, number> = new Map();
|
|
590
|
-
|
|
591
597
|
/** Strict or previously confirmed V2 target while acquire installs notifications. */
|
|
592
598
|
private acquiringProtocolV2 = new Set<string>();
|
|
593
599
|
|
|
@@ -652,11 +658,6 @@ export default class ReactNativeBleTransport {
|
|
|
652
658
|
init(logger: any, emitter: EventEmitter) {
|
|
653
659
|
setBleLogger(logger);
|
|
654
660
|
this.emitter = emitter;
|
|
655
|
-
if (Platform.OS === 'android') {
|
|
656
|
-
// Link security events are only meaningful if they were observed since the link came up.
|
|
657
|
-
startBleKeyMissingTracking();
|
|
658
|
-
startBleEncryptionTracking();
|
|
659
|
-
}
|
|
660
661
|
}
|
|
661
662
|
|
|
662
663
|
configure(signedData: any) {
|
|
@@ -1108,13 +1109,7 @@ export default class ReactNativeBleTransport {
|
|
|
1108
1109
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
1109
1110
|
}
|
|
1110
1111
|
|
|
1111
|
-
return this.runLifecycleOperation(uuid,
|
|
1112
|
-
try {
|
|
1113
|
-
return await this.acquireUnlocked(input);
|
|
1114
|
-
} catch (error) {
|
|
1115
|
-
throw await this.resolveAndroidBondInvalid(uuid, error);
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1112
|
+
return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
|
|
1118
1113
|
}
|
|
1119
1114
|
|
|
1120
1115
|
private async acquireUnlocked(input: FirmwareInstallBleAcquireInput) {
|
|
@@ -1194,8 +1189,6 @@ export default class ReactNativeBleTransport {
|
|
|
1194
1189
|
|
|
1195
1190
|
let device: Device | null = null;
|
|
1196
1191
|
const isAndroid = Platform.OS === 'android';
|
|
1197
|
-
// Only a bond that existed before this acquire can have been lost by the device.
|
|
1198
|
-
let androidBondedBeforeConnect = false;
|
|
1199
1192
|
// A firmware-install reconnect always refreshes: the new firmware may expose a different table.
|
|
1200
1193
|
const refreshAndroidGattCache =
|
|
1201
1194
|
isAndroid && (!!skipProtocolProbe || this.androidGattCacheRefreshes.has(uuid));
|
|
@@ -1227,19 +1220,11 @@ export default class ReactNativeBleTransport {
|
|
|
1227
1220
|
|
|
1228
1221
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1229
1222
|
if (Platform.OS === 'android') {
|
|
1230
|
-
// Subscribe before the link exists: key missing is broadcast while Android encrypts it.
|
|
1231
|
-
startBleKeyMissingTracking();
|
|
1232
|
-
startBleEncryptionTracking();
|
|
1233
|
-
// Failures before the new link starts must not be read against the previous link.
|
|
1234
|
-
this.androidLinkStartedAt.delete(uuid);
|
|
1235
1223
|
// Initiate bonding locally before GATT can trigger peripheral-initiated pairing.
|
|
1236
1224
|
try {
|
|
1237
1225
|
const bondState = await pairDevice(uuid);
|
|
1238
|
-
androidBondedBeforeConnect = !bondState.bonding && bondState.bonded;
|
|
1239
1226
|
if (bondState.bonding) {
|
|
1240
|
-
await onDeviceBondState(uuid, this.bondAbortController.signal
|
|
1241
|
-
systemInitiated: bondState.initiated === false,
|
|
1242
|
-
});
|
|
1227
|
+
await onDeviceBondState(uuid, this.bondAbortController.signal);
|
|
1243
1228
|
} else if (!bondState.bonded) {
|
|
1244
1229
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
1245
1230
|
}
|
|
@@ -1251,7 +1236,6 @@ export default class ReactNativeBleTransport {
|
|
|
1251
1236
|
});
|
|
1252
1237
|
throw error;
|
|
1253
1238
|
}
|
|
1254
|
-
this.androidLinkStartedAt.set(uuid, Date.now());
|
|
1255
1239
|
}
|
|
1256
1240
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1257
1241
|
|
|
@@ -1439,13 +1423,7 @@ export default class ReactNativeBleTransport {
|
|
|
1439
1423
|
this.acquiringProtocolV2.add(uuid);
|
|
1440
1424
|
}
|
|
1441
1425
|
|
|
1442
|
-
let linkEncryption: AndroidLinkEncryption | undefined;
|
|
1443
1426
|
try {
|
|
1444
|
-
// A firmware-install reconnect keeps its existing sequence.
|
|
1445
|
-
if (androidBondedBeforeConnect && !skipProtocolProbe) {
|
|
1446
|
-
linkEncryption = await this.waitForAndroidLinkSecurity(uuid);
|
|
1447
|
-
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1448
|
-
}
|
|
1449
1427
|
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
1450
1428
|
writeCharacteristic,
|
|
1451
1429
|
notifyCharacteristic,
|
|
@@ -1497,16 +1475,13 @@ export default class ReactNativeBleTransport {
|
|
|
1497
1475
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1498
1476
|
}
|
|
1499
1477
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1500
|
-
if (linkEncryption === 'unresolved') {
|
|
1501
|
-
// Notifications and the probe only work on an encrypted link, so the next reuse of this
|
|
1502
|
-
// link needs no result that was reported before tracking started.
|
|
1503
|
-
markBleLinkEncrypted(uuid);
|
|
1504
|
-
}
|
|
1505
1478
|
return { uuid, protocolType };
|
|
1506
1479
|
} catch (error) {
|
|
1507
1480
|
// A failed acquire must retire the physical link before Core retries. Logical
|
|
1508
1481
|
// release leaves GATT connected even when neither protocol receives a response.
|
|
1509
|
-
await this.disconnectUnlocked(uuid
|
|
1482
|
+
await this.disconnectUnlocked(uuid, {
|
|
1483
|
+
preserveIosProbeDisconnect: this.iosProbeDisconnectEvidence.has(uuid),
|
|
1484
|
+
});
|
|
1510
1485
|
throw error;
|
|
1511
1486
|
} finally {
|
|
1512
1487
|
this.acquiringProtocolV2.delete(uuid);
|
|
@@ -1770,77 +1745,16 @@ export default class ReactNativeBleTransport {
|
|
|
1770
1745
|
`Device protocol has not been detected for ${uuid}`
|
|
1771
1746
|
);
|
|
1772
1747
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
try {
|
|
1779
|
-
return protocol === 'V2'
|
|
1780
|
-
? await this.callProtocolV2(uuid, name, data, options)
|
|
1781
|
-
: await this.callProtocolV1(uuid, name, data, options);
|
|
1782
|
-
} catch (error) {
|
|
1783
|
-
// An expected-V1 acquire skips the probe, so the first call is what meets the dropped link.
|
|
1784
|
-
throw await this.resolveAndroidBondInvalid(uuid, error);
|
|
1748
|
+
if (protocol === 'V2') {
|
|
1749
|
+
return this.callProtocolV2(uuid, name, data, options);
|
|
1785
1750
|
}
|
|
1786
|
-
}
|
|
1787
1751
|
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
* connect is re-read as an invalid bond when that signal belongs to the same attempt.
|
|
1792
|
-
*/
|
|
1793
|
-
private async resolveAndroidBondInvalid(uuid: string, error: unknown): Promise<unknown> {
|
|
1794
|
-
if (Platform.OS !== 'android' || !isBleKeyMissingSupported()) return error;
|
|
1795
|
-
if (isBleStaleBondHardwareError(error) || !isAndroidLinkLossError(error)) return error;
|
|
1796
|
-
const linkStartedAt = this.androidLinkStartedAt.get(uuid);
|
|
1797
|
-
if (
|
|
1798
|
-
linkStartedAt === undefined ||
|
|
1799
|
-
Date.now() - linkStartedAt > ANDROID_KEY_MISSING_LINK_WINDOW_MS
|
|
1800
|
-
) {
|
|
1801
|
-
return error;
|
|
1802
|
-
}
|
|
1803
|
-
if (!(await waitForBleKeyMissing(uuid, linkStartedAt, ANDROID_KEY_MISSING_GRACE_MS))) {
|
|
1804
|
-
return error;
|
|
1752
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1753
|
+
if (this.runPromise && !forceRun) {
|
|
1754
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1805
1755
|
}
|
|
1806
|
-
Log?.debug('[ReactNativeBleTransport] Android key missing, bond is invalid:', uuid);
|
|
1807
|
-
return ERRORS.TypedError(HardwareErrorCode.BleBondInvalid, undefined, {
|
|
1808
|
-
phase: 'connect',
|
|
1809
|
-
reason: 'key_missing',
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
1756
|
|
|
1813
|
-
|
|
1814
|
-
* Android 16+ encrypts a bonded link on its own right after connecting. Subscribing to
|
|
1815
|
-
* notifications before that finishes gets the CCCD write rejected, and the framework's retry
|
|
1816
|
-
* encrypts again with the same stale key; firmware that allows one failure per link then drops
|
|
1817
|
-
* it before the system can re-pair. Holding GATT until the result lets a lost bond be re-paired
|
|
1818
|
-
* in place. Without the platform signal this resolves immediately.
|
|
1819
|
-
*/
|
|
1820
|
-
private async waitForAndroidLinkSecurity(uuid: string): Promise<AndroidLinkEncryption> {
|
|
1821
|
-
const linkStartedAt = this.androidLinkStartedAt.get(uuid);
|
|
1822
|
-
if (linkStartedAt === undefined) return 'unresolved';
|
|
1823
|
-
const waitStartedAt = Date.now();
|
|
1824
|
-
const result = await waitForAndroidLinkEncryption({
|
|
1825
|
-
deviceId: uuid,
|
|
1826
|
-
linkStartedAt,
|
|
1827
|
-
resultTimeoutMs: ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS,
|
|
1828
|
-
repairTimeoutMs: ANDROID_SYSTEM_REPAIR_TIMEOUT_MS,
|
|
1829
|
-
keyMissingGraceMs: ANDROID_KEY_MISSING_GRACE_MS,
|
|
1830
|
-
signal: this.bondAbortController.signal,
|
|
1831
|
-
onRepairStarted: () => {
|
|
1832
|
-
Log?.debug(
|
|
1833
|
-
'[ReactNativeBleTransport] Android bond lost, waiting for system re-pairing:',
|
|
1834
|
-
uuid
|
|
1835
|
-
);
|
|
1836
|
-
},
|
|
1837
|
-
});
|
|
1838
|
-
Log?.debug('[ReactNativeBleTransport] Android link encryption', {
|
|
1839
|
-
connectIdSuffix: uuid.slice(-8),
|
|
1840
|
-
result,
|
|
1841
|
-
waitedMs: Date.now() - waitStartedAt,
|
|
1842
|
-
});
|
|
1843
|
-
return result;
|
|
1757
|
+
return this.callProtocolV1(uuid, name, data, options);
|
|
1844
1758
|
}
|
|
1845
1759
|
|
|
1846
1760
|
private async callProtocolV1(
|
|
@@ -2108,9 +2022,7 @@ export default class ReactNativeBleTransport {
|
|
|
2108
2022
|
this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
|
|
2109
2023
|
this.androidPriorityResetTimers.clear();
|
|
2110
2024
|
this.androidHighPriorityDevices.clear();
|
|
2111
|
-
this.
|
|
2112
|
-
stopBleKeyMissingTracking();
|
|
2113
|
-
stopBleEncryptionTracking();
|
|
2025
|
+
this.iosProbeDisconnectEvidence.clear();
|
|
2114
2026
|
const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
2115
2027
|
this.runPromise?.reject(error);
|
|
2116
2028
|
this.runPromise = null;
|
|
@@ -2146,7 +2058,10 @@ export default class ReactNativeBleTransport {
|
|
|
2146
2058
|
return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
|
|
2147
2059
|
}
|
|
2148
2060
|
|
|
2149
|
-
private async disconnectUnlocked(
|
|
2061
|
+
private async disconnectUnlocked(
|
|
2062
|
+
session: string,
|
|
2063
|
+
options?: { preserveIosProbeDisconnect?: boolean }
|
|
2064
|
+
) {
|
|
2150
2065
|
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
2151
2066
|
const transport = transportCache[session];
|
|
2152
2067
|
const manager = this.blePlxManager;
|
|
@@ -2188,6 +2103,9 @@ export default class ReactNativeBleTransport {
|
|
|
2188
2103
|
this.deviceProtocolHints.delete(session);
|
|
2189
2104
|
this.sessionProtocols.delete(session);
|
|
2190
2105
|
this.protocolReprobeFailures.delete(session);
|
|
2106
|
+
if (!options?.preserveIosProbeDisconnect) {
|
|
2107
|
+
this.iosProbeDisconnectEvidence.delete(session);
|
|
2108
|
+
}
|
|
2191
2109
|
this.protocolV2Assemblers.delete(session);
|
|
2192
2110
|
this.resetProtocolV2Frames(session);
|
|
2193
2111
|
|
|
@@ -2536,6 +2454,7 @@ export default class ReactNativeBleTransport {
|
|
|
2536
2454
|
this.deviceProtocol.delete(uuid);
|
|
2537
2455
|
this.probingProtocols.delete(uuid);
|
|
2538
2456
|
this.staleBondErrors.delete(uuid);
|
|
2457
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2539
2458
|
this.acquiringProtocolV2.delete(uuid);
|
|
2540
2459
|
this.protocolV2Assemblers.delete(uuid);
|
|
2541
2460
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -2634,6 +2553,7 @@ export default class ReactNativeBleTransport {
|
|
|
2634
2553
|
this.deviceProtocol.delete(uuid);
|
|
2635
2554
|
this.probingProtocols.delete(uuid);
|
|
2636
2555
|
this.staleBondErrors.delete(uuid);
|
|
2556
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2637
2557
|
this.acquiringProtocolV2.delete(uuid);
|
|
2638
2558
|
this.protocolV2Assemblers.delete(uuid);
|
|
2639
2559
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -2687,6 +2607,7 @@ export default class ReactNativeBleTransport {
|
|
|
2687
2607
|
this.deviceProtocol.clear();
|
|
2688
2608
|
this.probingProtocols.clear();
|
|
2689
2609
|
this.staleBondErrors.clear();
|
|
2610
|
+
this.iosProbeDisconnectEvidence.clear();
|
|
2690
2611
|
this.acquiringProtocolV2.clear();
|
|
2691
2612
|
this.sessionProtocols.clear();
|
|
2692
2613
|
// Keep transport-lifetime V2 proof so the same endpoint can finish a no-probe
|
|
@@ -2730,6 +2651,47 @@ export default class ReactNativeBleTransport {
|
|
|
2730
2651
|
);
|
|
2731
2652
|
}
|
|
2732
2653
|
|
|
2654
|
+
private rememberIosProbeDisconnect(
|
|
2655
|
+
uuid: string,
|
|
2656
|
+
phase: IosProbeDisconnectEvidence['phase'],
|
|
2657
|
+
error: unknown
|
|
2658
|
+
): Error | undefined {
|
|
2659
|
+
if (!isIosProbeDisconnectError(error)) return undefined;
|
|
2660
|
+
|
|
2661
|
+
const previous = this.iosProbeDisconnectEvidence.get(uuid);
|
|
2662
|
+
const errorCode = (error as { errorCode?: unknown })?.errorCode;
|
|
2663
|
+
const evidence: IosProbeDisconnectEvidence = {
|
|
2664
|
+
phase,
|
|
2665
|
+
...(typeof errorCode === 'string' || typeof errorCode === 'number' ? { errorCode } : {}),
|
|
2666
|
+
};
|
|
2667
|
+
this.iosProbeDisconnectEvidence.set(uuid, evidence);
|
|
2668
|
+
Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe disconnect recorded', {
|
|
2669
|
+
phase,
|
|
2670
|
+
errorCode: evidence.errorCode,
|
|
2671
|
+
repeated: Boolean(previous),
|
|
2672
|
+
});
|
|
2673
|
+
|
|
2674
|
+
if (!previous) return undefined;
|
|
2675
|
+
return this.consumeIosProbeDisconnect(uuid, 'repeated-probe-disconnect');
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
private consumeIosProbeDisconnect(
|
|
2679
|
+
uuid: string,
|
|
2680
|
+
confirmation: 'repeated-probe-disconnect' | 'follow-up-no-response'
|
|
2681
|
+
): Error | undefined {
|
|
2682
|
+
const evidence = this.iosProbeDisconnectEvidence.get(uuid);
|
|
2683
|
+
if (!evidence) return undefined;
|
|
2684
|
+
|
|
2685
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2686
|
+
Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] stale bond confirmed', {
|
|
2687
|
+
confirmation,
|
|
2688
|
+
triggerPhase: evidence.phase,
|
|
2689
|
+
triggerErrorCode: evidence.errorCode,
|
|
2690
|
+
finalErrorCode: HardwareErrorCode.BleBondInvalid,
|
|
2691
|
+
});
|
|
2692
|
+
return ERRORS.TypedError(HardwareErrorCode.BleBondInvalid);
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2733
2695
|
private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
|
|
2734
2696
|
if (this.probingProtocols.get(uuid) === protocol) {
|
|
2735
2697
|
this.probingProtocols.delete(uuid);
|
|
@@ -2761,6 +2723,7 @@ export default class ReactNativeBleTransport {
|
|
|
2761
2723
|
// received no response. skipProtocolProbe does not need it either — its only
|
|
2762
2724
|
// caller is the V2 firmware-install reconnect.
|
|
2763
2725
|
if (expectedProtocol === 'V1') {
|
|
2726
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2764
2727
|
this.deviceProtocol.set(uuid, 'V1');
|
|
2765
2728
|
Log?.debug('[ReactNativeBleTransport] protocol selected', {
|
|
2766
2729
|
deviceId: uuid,
|
|
@@ -2776,6 +2739,7 @@ export default class ReactNativeBleTransport {
|
|
|
2776
2739
|
|
|
2777
2740
|
if (expectedProtocol === 'V2') {
|
|
2778
2741
|
if (await this.probeProtocolV2(uuid)) {
|
|
2742
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2779
2743
|
this.deviceProtocol.set(uuid, 'V2');
|
|
2780
2744
|
this.sessionProtocols.set(uuid, 'V2');
|
|
2781
2745
|
this.confirmedProtocolV2.add(uuid);
|
|
@@ -2786,6 +2750,18 @@ export default class ReactNativeBleTransport {
|
|
|
2786
2750
|
});
|
|
2787
2751
|
return 'V2';
|
|
2788
2752
|
}
|
|
2753
|
+
const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
|
|
2754
|
+
if (Platform.OS === 'ios') {
|
|
2755
|
+
Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
|
|
2756
|
+
outcome: 'expected-v2-no-response',
|
|
2757
|
+
expectedProtocol,
|
|
2758
|
+
protocolHint: protocolHint ?? 'none',
|
|
2759
|
+
finalErrorCode: staleBondError
|
|
2760
|
+
? HardwareErrorCode.BleBondInvalid
|
|
2761
|
+
: HardwareErrorCode.RuntimeError,
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
if (staleBondError) throw staleBondError;
|
|
2789
2765
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
2790
2766
|
}
|
|
2791
2767
|
|
|
@@ -2821,6 +2797,7 @@ export default class ReactNativeBleTransport {
|
|
|
2821
2797
|
const detected =
|
|
2822
2798
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
2823
2799
|
if (detected) {
|
|
2800
|
+
this.iosProbeDisconnectEvidence.delete(uuid);
|
|
2824
2801
|
this.deviceProtocol.set(uuid, protocol);
|
|
2825
2802
|
this.sessionProtocols.set(uuid, protocol);
|
|
2826
2803
|
if (protocol === 'V2') {
|
|
@@ -2850,6 +2827,25 @@ export default class ReactNativeBleTransport {
|
|
|
2850
2827
|
|
|
2851
2828
|
this.deviceProtocol.delete(uuid);
|
|
2852
2829
|
this.probingProtocols.delete(uuid);
|
|
2830
|
+
const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
|
|
2831
|
+
if (Platform.OS === 'ios') {
|
|
2832
|
+
Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
|
|
2833
|
+
outcome: 'all-probes-no-response',
|
|
2834
|
+
expectedProtocol: expectedProtocol ?? 'none',
|
|
2835
|
+
protocolHint: protocolHint ?? 'none',
|
|
2836
|
+
sessionProtocol: sessionProtocol ?? 'none',
|
|
2837
|
+
probeOrder: probeOrder.join(','),
|
|
2838
|
+
trustSessionProtocol,
|
|
2839
|
+
reprobeFailures,
|
|
2840
|
+
transportCached: Boolean(transportCache[uuid]),
|
|
2841
|
+
notifyActive: Boolean(transportCache[uuid]?.notifySubscription),
|
|
2842
|
+
staleBondRecorded: this.staleBondErrors.has(uuid),
|
|
2843
|
+
finalErrorCode: staleBondError
|
|
2844
|
+
? HardwareErrorCode.BleBondInvalid
|
|
2845
|
+
: HardwareErrorCode.BleTimeoutError,
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
if (staleBondError) throw staleBondError;
|
|
2853
2849
|
throw this.createProtocolDetectionError();
|
|
2854
2850
|
}
|
|
2855
2851
|
|
|
@@ -2948,7 +2944,19 @@ export default class ReactNativeBleTransport {
|
|
|
2948
2944
|
} catch (error) {
|
|
2949
2945
|
this.clearProbeProtocol(uuid, 'V1');
|
|
2950
2946
|
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
2951
|
-
|
|
2947
|
+
const shouldRethrow = shouldRethrowProtocolProbeError(error);
|
|
2948
|
+
const staleBondError = this.rememberIosProbeDisconnect(
|
|
2949
|
+
uuid,
|
|
2950
|
+
'protocol-v1-get-features',
|
|
2951
|
+
error
|
|
2952
|
+
);
|
|
2953
|
+
logIosBleBondDiagnostic(
|
|
2954
|
+
'protocol-v1-get-features',
|
|
2955
|
+
error,
|
|
2956
|
+
shouldRethrow ? 'rethrow' : 'fallback'
|
|
2957
|
+
);
|
|
2958
|
+
if (staleBondError) throw staleBondError;
|
|
2959
|
+
if (shouldRethrow) {
|
|
2952
2960
|
throw error;
|
|
2953
2961
|
}
|
|
2954
2962
|
return false;
|
|
@@ -2963,18 +2971,26 @@ export default class ReactNativeBleTransport {
|
|
|
2963
2971
|
this.probingProtocols.set(uuid, 'V2');
|
|
2964
2972
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2965
2973
|
this.throwIfStaleBondError(uuid);
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2974
|
+
let detected: boolean;
|
|
2975
|
+
try {
|
|
2976
|
+
detected = await probeProtocolV2Helper({
|
|
2977
|
+
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
2978
|
+
this.callProtocolV2(uuid, name, data, options),
|
|
2979
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
2980
|
+
logger: Log,
|
|
2981
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
2982
|
+
onProbeFailed: () => {
|
|
2983
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2984
|
+
this.resetProtocolV2Frames(uuid);
|
|
2985
|
+
},
|
|
2986
|
+
shouldRethrow: shouldRethrowProtocolProbeError,
|
|
2987
|
+
});
|
|
2988
|
+
} catch (error) {
|
|
2989
|
+
const staleBondError = this.rememberIosProbeDisconnect(uuid, 'protocol-v2-ping', error);
|
|
2990
|
+
logIosBleBondDiagnostic('protocol-v2-ping', error, 'rethrow');
|
|
2991
|
+
if (staleBondError) throw staleBondError;
|
|
2992
|
+
throw error;
|
|
2993
|
+
}
|
|
2978
2994
|
if (!detected) {
|
|
2979
2995
|
this.clearProbeProtocol(uuid, 'V2');
|
|
2980
2996
|
} else {
|
package/dist/bleEncryption.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export declare const HCI_PIN_OR_KEY_MISSING = 6;
|
|
2
|
-
export type AndroidLinkEncryption = 'encrypted' | 'already-encrypted' | 're-paired' | 'unresolved';
|
|
3
|
-
export declare const isBleEncryptionTrackingSupported: () => boolean;
|
|
4
|
-
export declare const startBleEncryptionTracking: () => boolean;
|
|
5
|
-
export declare const stopBleEncryptionTracking: () => void;
|
|
6
|
-
export declare const markBleLinkEncrypted: (deviceId: string) => void;
|
|
7
|
-
export type WaitForAndroidLinkEncryptionOptions = {
|
|
8
|
-
deviceId: string;
|
|
9
|
-
linkStartedAt: number;
|
|
10
|
-
resultTimeoutMs: number;
|
|
11
|
-
repairTimeoutMs: number;
|
|
12
|
-
keyMissingGraceMs: number;
|
|
13
|
-
signal?: AbortSignal;
|
|
14
|
-
onRepairStarted?: () => void;
|
|
15
|
-
};
|
|
16
|
-
export declare const waitForAndroidLinkEncryption: ({ deviceId, linkStartedAt, resultTimeoutMs, repairTimeoutMs, keyMissingGraceMs, signal, onRepairStarted, }: WaitForAndroidLinkEncryptionOptions) => Promise<AndroidLinkEncryption>;
|
|
17
|
-
//# sourceMappingURL=bleEncryption.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bleEncryption.d.ts","sourceRoot":"","sources":["../src/bleEncryption.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,sBAAsB,IAAO,CAAC;AAE3C,MAAM,MAAM,qBAAqB,GAE7B,WAAW,GAEX,mBAAmB,GAEnB,WAAW,GAEX,YAAY,CAAC;AA0CjB,eAAO,MAAM,gCAAgC,eAA2C,CAAC;AAGzF,eAAO,MAAM,0BAA0B,QAAO,OAwB7C,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAMrC,CAAC;AAMF,eAAO,MAAM,oBAAoB,aAAc,MAAM,SAGpD,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,EAAE,MAAM,CAAC;IAEtB,eAAe,EAAE,MAAM,CAAC;IAExB,eAAe,EAAE,MAAM,CAAC;IAExB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAOF,eAAO,MAAM,4BAA4B,+GAQtC,mCAAmC,KAAG,QAAQ,qBAAqB,CAuHrE,CAAC"}
|
package/dist/bleKeyMissing.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const isBleKeyMissingSupported: () => boolean;
|
|
2
|
-
export declare const startBleKeyMissingTracking: () => boolean;
|
|
3
|
-
export declare const stopBleKeyMissingTracking: () => void;
|
|
4
|
-
export declare const hasBleKeyMissingSince: (deviceId: string, sinceMs: number) => boolean;
|
|
5
|
-
export declare const onBleKeyMissing: (deviceId: string, callback: () => void) => (() => void);
|
|
6
|
-
export declare const waitForBleKeyMissing: (deviceId: string, sinceMs: number, timeoutMs: number) => Promise<boolean>;
|
|
7
|
-
//# sourceMappingURL=bleKeyMissing.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bleKeyMissing.d.ts","sourceRoot":"","sources":["../src/bleKeyMissing.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,wBAAwB,eAA2C,CAAC;AAGjF,eAAO,MAAM,0BAA0B,QAAO,OAY7C,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAKrC,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,KAAG,OAGzE,CAAC;AAGF,eAAO,MAAM,eAAe,aAAc,MAAM,YAAY,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CASnF,CAAC;AAOF,eAAO,MAAM,oBAAoB,aACrB,MAAM,WACP,MAAM,aACJ,MAAM,KAChB,QAAQ,OAAO,CAejB,CAAC"}
|