@onekeyfe/hd-transport-react-native 1.2.0-alpha.66 → 1.2.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +79 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +298 -40
- package/jest.config.js +5 -0
- package/package.json +5 -5
- package/src/__tests__/connectTimeout.test.ts +281 -0
- package/src/__tests__/protocolReprobe.test.ts +132 -0
- package/src/__tests__/protocolV1SchemaFixture.ts +39 -0
- package/src/__tests__/protocolV2Link.test.ts +10 -1
- package/src/__tests__/staleCallTimeout.test.ts +210 -0
- package/src/__tests__/writePacketTimeout.test.ts +305 -0
- package/src/index.ts +449 -39
package/dist/index.js
CHANGED
|
@@ -275,6 +275,12 @@ const getFirmwareUploadWriteRetryType = (error) => {
|
|
|
275
275
|
const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
276
276
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
277
277
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
|
|
278
|
+
const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
|
|
279
|
+
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
280
|
+
const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
|
|
281
|
+
typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
|
|
282
|
+
error.message.startsWith(WEDGED_WRITE_MESSAGE);
|
|
283
|
+
const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
278
284
|
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
279
285
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
280
286
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
@@ -310,11 +316,24 @@ function getDeviceDisplayName(device) {
|
|
|
310
316
|
return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
|
|
311
317
|
}
|
|
312
318
|
const ANDROID_REQUEST_MTU = 256;
|
|
319
|
+
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
313
320
|
const connectOptions = {
|
|
314
321
|
requestMTU: ANDROID_REQUEST_MTU,
|
|
315
|
-
timeout:
|
|
322
|
+
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
316
323
|
refreshGatt: 'OnConnected',
|
|
317
324
|
};
|
|
325
|
+
const fallbackConnectOptions = {
|
|
326
|
+
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
327
|
+
};
|
|
328
|
+
const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
329
|
+
const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
|
|
330
|
+
const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
331
|
+
const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
332
|
+
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
333
|
+
const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
|
|
334
|
+
typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
|
|
335
|
+
error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
336
|
+
const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
|
|
318
337
|
const tryToGetConfiguration = (device) => {
|
|
319
338
|
if (!device || !device.serviceUUIDs)
|
|
320
339
|
return null;
|
|
@@ -372,7 +391,12 @@ class ReactNativeBleTransport {
|
|
|
372
391
|
this.runPromiseDeviceId = null;
|
|
373
392
|
this.firmwareUploadWriteRecoveryIds = new Set();
|
|
374
393
|
this.deviceProtocol = new Map();
|
|
394
|
+
this.probingProtocols = new Map();
|
|
395
|
+
this.writeTimeoutCounts = new Map();
|
|
396
|
+
this.connectionSetupTimeoutCounts = new Map();
|
|
375
397
|
this.deviceProtocolHints = new Map();
|
|
398
|
+
this.sessionProtocols = new Map();
|
|
399
|
+
this.protocolReprobeFailures = new Map();
|
|
376
400
|
this.protocolV2Assemblers = new Map();
|
|
377
401
|
this.protocolV2FrameQueues = new Map();
|
|
378
402
|
this.protocolV2FramePromises = new Map();
|
|
@@ -564,19 +588,19 @@ class ReactNativeBleTransport {
|
|
|
564
588
|
const isConnected = yield device.isConnected().catch(() => false);
|
|
565
589
|
if (!isConnected) {
|
|
566
590
|
try {
|
|
567
|
-
device = yield device.connect(connectOptions);
|
|
591
|
+
device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
|
|
568
592
|
}
|
|
569
593
|
catch (e) {
|
|
570
594
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
571
595
|
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
572
|
-
device = yield device.connect();
|
|
596
|
+
device = yield this.connectWithTimeout(uuid, () => device.connect());
|
|
573
597
|
}
|
|
574
598
|
else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
|
|
575
599
|
throw e;
|
|
576
600
|
}
|
|
577
601
|
}
|
|
578
602
|
}
|
|
579
|
-
const { writeCharacteristic, notifyCharacteristic } = yield this.
|
|
603
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
|
|
580
604
|
transport.device = device;
|
|
581
605
|
transport.writeCharacteristic = writeCharacteristic;
|
|
582
606
|
transport.notifyCharacteristic = notifyCharacteristic;
|
|
@@ -713,7 +737,7 @@ class ReactNativeBleTransport {
|
|
|
713
737
|
}
|
|
714
738
|
installTransportForAcquire(uuid, device, characteristics) {
|
|
715
739
|
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
-
const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.
|
|
740
|
+
const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
717
741
|
const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
718
742
|
if (reactNative.Platform.OS === 'android') {
|
|
719
743
|
transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
|
|
@@ -796,14 +820,17 @@ class ReactNativeBleTransport {
|
|
|
796
820
|
if (!device) {
|
|
797
821
|
Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
|
|
798
822
|
try {
|
|
799
|
-
device = yield blePlxManager.connectToDevice(uuid, connectOptions);
|
|
823
|
+
device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
|
|
800
824
|
}
|
|
801
825
|
catch (e) {
|
|
802
826
|
Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
|
|
827
|
+
if (isConnectTimeoutError(e)) {
|
|
828
|
+
throw e;
|
|
829
|
+
}
|
|
803
830
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
804
831
|
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
805
832
|
Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
|
|
806
|
-
device = yield blePlxManager.connectToDevice(uuid);
|
|
833
|
+
device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
|
|
807
834
|
}
|
|
808
835
|
else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
|
|
809
836
|
Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
|
|
@@ -819,23 +846,27 @@ class ReactNativeBleTransport {
|
|
|
819
846
|
}
|
|
820
847
|
if (!(yield device.isConnected())) {
|
|
821
848
|
Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
|
|
849
|
+
const disconnectedDevice = device;
|
|
822
850
|
try {
|
|
823
|
-
device = yield
|
|
851
|
+
device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
|
|
824
852
|
}
|
|
825
853
|
catch (e) {
|
|
826
854
|
Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
|
|
855
|
+
if (isConnectTimeoutError(e)) {
|
|
856
|
+
throw e;
|
|
857
|
+
}
|
|
827
858
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
828
859
|
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
829
860
|
Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
|
|
830
861
|
try {
|
|
831
|
-
device = yield
|
|
862
|
+
device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
|
|
832
863
|
}
|
|
833
864
|
catch (e) {
|
|
834
865
|
Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
|
|
835
866
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
836
867
|
Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
|
|
837
|
-
yield
|
|
838
|
-
device = yield
|
|
868
|
+
yield disconnectedDevice.cancelConnection();
|
|
869
|
+
device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
|
|
839
870
|
}
|
|
840
871
|
}
|
|
841
872
|
}
|
|
@@ -846,7 +877,7 @@ class ReactNativeBleTransport {
|
|
|
846
877
|
}
|
|
847
878
|
device = yield requestAndroidMtu(device);
|
|
848
879
|
const acquiredDevice = device;
|
|
849
|
-
const { writeCharacteristic, notifyCharacteristic } = yield this.
|
|
880
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
850
881
|
const protocolHint = expectedProtocol
|
|
851
882
|
? undefined
|
|
852
883
|
: (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
@@ -891,7 +922,7 @@ class ReactNativeBleTransport {
|
|
|
891
922
|
Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
892
923
|
return;
|
|
893
924
|
}
|
|
894
|
-
if (this.
|
|
925
|
+
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
895
926
|
let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
|
|
896
927
|
if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
|
|
897
928
|
errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
|
|
@@ -942,7 +973,7 @@ class ReactNativeBleTransport {
|
|
|
942
973
|
}
|
|
943
974
|
try {
|
|
944
975
|
const data = buffer.Buffer.from(c.value, 'base64');
|
|
945
|
-
const protocol = this.
|
|
976
|
+
const protocol = this.getActiveProtocol(uuid);
|
|
946
977
|
if (!protocol) {
|
|
947
978
|
Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
|
|
948
979
|
return;
|
|
@@ -970,7 +1001,7 @@ class ReactNativeBleTransport {
|
|
|
970
1001
|
catch (error) {
|
|
971
1002
|
Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
|
|
972
1003
|
const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
973
|
-
if (this.
|
|
1004
|
+
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
974
1005
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
975
1006
|
}
|
|
976
1007
|
else if (this.runPromiseDeviceId === uuid) {
|
|
@@ -1026,6 +1057,7 @@ class ReactNativeBleTransport {
|
|
|
1026
1057
|
delete transportCache[uuid];
|
|
1027
1058
|
}
|
|
1028
1059
|
this.deviceProtocol.delete(uuid);
|
|
1060
|
+
this.probingProtocols.delete(uuid);
|
|
1029
1061
|
(_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
|
|
1030
1062
|
this.protocolV2Assemblers.delete(uuid);
|
|
1031
1063
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1074,8 +1106,19 @@ class ReactNativeBleTransport {
|
|
|
1074
1106
|
const transport = this.getCachedTransport(uuid);
|
|
1075
1107
|
const runPromise = hdShared.createDeferred();
|
|
1076
1108
|
runPromise.promise.catch(() => undefined);
|
|
1109
|
+
const supersededRunPromise = this.runPromise;
|
|
1110
|
+
if (supersededRunPromise) {
|
|
1111
|
+
supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
|
|
1112
|
+
}
|
|
1077
1113
|
this.runPromise = runPromise;
|
|
1078
1114
|
this.runPromiseDeviceId = uuid;
|
|
1115
|
+
const releaseOwnershipIfCurrent = () => {
|
|
1116
|
+
if (this.runPromise === runPromise) {
|
|
1117
|
+
this.runPromise = null;
|
|
1118
|
+
this.runPromiseDeviceId = null;
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
const isCurrentOwner = () => this.runPromise === runPromise;
|
|
1079
1122
|
const messages = this._messages;
|
|
1080
1123
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1081
1124
|
let timeout;
|
|
@@ -1096,6 +1139,9 @@ class ReactNativeBleTransport {
|
|
|
1096
1139
|
}
|
|
1097
1140
|
catch (e) {
|
|
1098
1141
|
onError(e);
|
|
1142
|
+
if (isWedgedWriteError(e)) {
|
|
1143
|
+
throw e;
|
|
1144
|
+
}
|
|
1099
1145
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
1100
1146
|
}
|
|
1101
1147
|
}
|
|
@@ -1123,6 +1169,9 @@ class ReactNativeBleTransport {
|
|
|
1123
1169
|
}
|
|
1124
1170
|
catch (e) {
|
|
1125
1171
|
onError(e);
|
|
1172
|
+
if (isWedgedWriteError(e)) {
|
|
1173
|
+
throw e;
|
|
1174
|
+
}
|
|
1126
1175
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
|
|
1127
1176
|
}
|
|
1128
1177
|
}
|
|
@@ -1133,8 +1182,8 @@ class ReactNativeBleTransport {
|
|
|
1133
1182
|
});
|
|
1134
1183
|
}
|
|
1135
1184
|
if (name === 'EmmcFileWrite') {
|
|
1136
|
-
yield writeChunkedData(buffers, data => transport.writeWithRetry(
|
|
1137
|
-
|
|
1185
|
+
yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
|
|
1186
|
+
releaseOwnershipIfCurrent();
|
|
1138
1187
|
Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
|
|
1139
1188
|
});
|
|
1140
1189
|
}
|
|
@@ -1150,7 +1199,7 @@ class ReactNativeBleTransport {
|
|
|
1150
1199
|
let attempt = 0;
|
|
1151
1200
|
while (true) {
|
|
1152
1201
|
try {
|
|
1153
|
-
yield transport.writeWithRetry(
|
|
1202
|
+
yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
|
|
1154
1203
|
return;
|
|
1155
1204
|
}
|
|
1156
1205
|
catch (error) {
|
|
@@ -1169,7 +1218,7 @@ class ReactNativeBleTransport {
|
|
|
1169
1218
|
}
|
|
1170
1219
|
}
|
|
1171
1220
|
}), e => {
|
|
1172
|
-
|
|
1221
|
+
releaseOwnershipIfCurrent();
|
|
1173
1222
|
Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
|
|
1174
1223
|
});
|
|
1175
1224
|
}
|
|
@@ -1178,16 +1227,16 @@ class ReactNativeBleTransport {
|
|
|
1178
1227
|
const outData = o.toString('base64');
|
|
1179
1228
|
try {
|
|
1180
1229
|
const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
else {
|
|
1185
|
-
yield transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1186
|
-
}
|
|
1230
|
+
yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
|
|
1231
|
+
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
1232
|
+
: transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
|
|
1187
1233
|
}
|
|
1188
1234
|
catch (e) {
|
|
1189
1235
|
Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
|
|
1190
|
-
|
|
1236
|
+
releaseOwnershipIfCurrent();
|
|
1237
|
+
if (isWedgedWriteError(e)) {
|
|
1238
|
+
throw e;
|
|
1239
|
+
}
|
|
1191
1240
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
|
|
1192
1241
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
|
|
1193
1242
|
}
|
|
@@ -1227,7 +1276,9 @@ class ReactNativeBleTransport {
|
|
|
1227
1276
|
Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
|
|
1228
1277
|
}
|
|
1229
1278
|
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1279
|
+
const isStaleCall = this.runPromise !== runPromise;
|
|
1230
1280
|
if (!isProbeTimeout &&
|
|
1281
|
+
!isStaleCall &&
|
|
1231
1282
|
(e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1232
1283
|
yield this.disconnect(uuid);
|
|
1233
1284
|
}
|
|
@@ -1298,7 +1349,10 @@ class ReactNativeBleTransport {
|
|
|
1298
1349
|
delete transportCache[session];
|
|
1299
1350
|
}
|
|
1300
1351
|
this.deviceProtocol.delete(session);
|
|
1352
|
+
this.probingProtocols.delete(session);
|
|
1301
1353
|
this.deviceProtocolHints.delete(session);
|
|
1354
|
+
this.sessionProtocols.delete(session);
|
|
1355
|
+
this.protocolReprobeFailures.delete(session);
|
|
1302
1356
|
this.protocolV2Assemblers.delete(session);
|
|
1303
1357
|
this.resetProtocolV2Frames(session);
|
|
1304
1358
|
try {
|
|
@@ -1319,6 +1373,91 @@ class ReactNativeBleTransport {
|
|
|
1319
1373
|
this.runPromise = null;
|
|
1320
1374
|
this.runPromiseDeviceId = null;
|
|
1321
1375
|
}
|
|
1376
|
+
connectWithTimeout(uuid, connect) {
|
|
1377
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1378
|
+
let timer;
|
|
1379
|
+
let timedOut = false;
|
|
1380
|
+
const pending = connect();
|
|
1381
|
+
pending.catch(() => undefined);
|
|
1382
|
+
try {
|
|
1383
|
+
const result = yield Promise.race([
|
|
1384
|
+
pending,
|
|
1385
|
+
new Promise((_, reject) => {
|
|
1386
|
+
timer = setTimeout(() => {
|
|
1387
|
+
timedOut = true;
|
|
1388
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
|
|
1389
|
+
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1390
|
+
}),
|
|
1391
|
+
]);
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1394
|
+
catch (error) {
|
|
1395
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1396
|
+
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1397
|
+
}
|
|
1398
|
+
throw error;
|
|
1399
|
+
}
|
|
1400
|
+
finally {
|
|
1401
|
+
if (timer)
|
|
1402
|
+
clearTimeout(timer);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
resolveCharacteristicsWithTimeout(uuid, device) {
|
|
1407
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1408
|
+
let timer;
|
|
1409
|
+
let timedOut = false;
|
|
1410
|
+
const pending = this.resolveCharacteristics(device);
|
|
1411
|
+
pending.catch(() => undefined);
|
|
1412
|
+
try {
|
|
1413
|
+
const result = yield Promise.race([
|
|
1414
|
+
pending,
|
|
1415
|
+
new Promise((_, reject) => {
|
|
1416
|
+
timer = setTimeout(() => {
|
|
1417
|
+
timedOut = true;
|
|
1418
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
|
|
1419
|
+
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1420
|
+
}),
|
|
1421
|
+
]);
|
|
1422
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1423
|
+
return result;
|
|
1424
|
+
}
|
|
1425
|
+
catch (error) {
|
|
1426
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1427
|
+
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1428
|
+
}
|
|
1429
|
+
throw error;
|
|
1430
|
+
}
|
|
1431
|
+
finally {
|
|
1432
|
+
if (timer)
|
|
1433
|
+
clearTimeout(timer);
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
abandonStalledConnection(uuid, stage) {
|
|
1438
|
+
var _a, _b;
|
|
1439
|
+
const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
1440
|
+
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1441
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1442
|
+
stage,
|
|
1443
|
+
setupTimeoutsSinceSuccess: timeouts,
|
|
1444
|
+
});
|
|
1445
|
+
(_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
|
|
1446
|
+
});
|
|
1447
|
+
const stalled = transportCache[uuid];
|
|
1448
|
+
if (stalled) {
|
|
1449
|
+
delete transportCache[uuid];
|
|
1450
|
+
}
|
|
1451
|
+
this.deviceProtocol.delete(uuid);
|
|
1452
|
+
this.probingProtocols.delete(uuid);
|
|
1453
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
1454
|
+
this.resetProtocolV2Frames(uuid);
|
|
1455
|
+
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1456
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1457
|
+
this.resetPlxManager();
|
|
1458
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1322
1461
|
getCachedTransport(uuid) {
|
|
1323
1462
|
const transport = transportCache[uuid];
|
|
1324
1463
|
if (!transport) {
|
|
@@ -1326,6 +1465,82 @@ class ReactNativeBleTransport {
|
|
|
1326
1465
|
}
|
|
1327
1466
|
return transport;
|
|
1328
1467
|
}
|
|
1468
|
+
writeBlePacket(uuid, data, write, isCurrentOwner) {
|
|
1469
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1470
|
+
let timer;
|
|
1471
|
+
let timedOut = false;
|
|
1472
|
+
try {
|
|
1473
|
+
yield Promise.race([
|
|
1474
|
+
write(data),
|
|
1475
|
+
new Promise((_, reject) => {
|
|
1476
|
+
timer = setTimeout(() => {
|
|
1477
|
+
timedOut = true;
|
|
1478
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
|
|
1479
|
+
}, BLE_WRITE_PACKET_TIMEOUT_MS);
|
|
1480
|
+
}),
|
|
1481
|
+
]);
|
|
1482
|
+
this.writeTimeoutCounts.delete(uuid);
|
|
1483
|
+
}
|
|
1484
|
+
catch (error) {
|
|
1485
|
+
if (timedOut) {
|
|
1486
|
+
if (isCurrentOwner && !isCurrentOwner()) {
|
|
1487
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
this.tearDownWedgedLink(uuid);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
throw error;
|
|
1494
|
+
}
|
|
1495
|
+
finally {
|
|
1496
|
+
if (timer)
|
|
1497
|
+
clearTimeout(timer);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
tearDownWedgedLink(uuid) {
|
|
1502
|
+
var _a;
|
|
1503
|
+
const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
1504
|
+
this.writeTimeoutCounts.set(uuid, timeouts);
|
|
1505
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
|
|
1506
|
+
consecutiveWriteTimeouts: timeouts,
|
|
1507
|
+
});
|
|
1508
|
+
const wedged = transportCache[uuid];
|
|
1509
|
+
this.disconnect(uuid).catch(error => {
|
|
1510
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
|
|
1511
|
+
});
|
|
1512
|
+
if (wedged && transportCache[uuid] === wedged) {
|
|
1513
|
+
delete transportCache[uuid];
|
|
1514
|
+
}
|
|
1515
|
+
this.deviceProtocol.delete(uuid);
|
|
1516
|
+
this.probingProtocols.delete(uuid);
|
|
1517
|
+
this.protocolV2Assemblers.delete(uuid);
|
|
1518
|
+
this.resetProtocolV2Frames(uuid);
|
|
1519
|
+
if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1520
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
|
|
1521
|
+
this.resetPlxManager();
|
|
1522
|
+
this.writeTimeoutCounts.delete(uuid);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
resetPlxManager() {
|
|
1526
|
+
const manager = this.blePlxManager;
|
|
1527
|
+
this.blePlxManager = undefined;
|
|
1528
|
+
Object.keys(transportCache).forEach(key => {
|
|
1529
|
+
delete transportCache[key];
|
|
1530
|
+
});
|
|
1531
|
+
this.deviceProtocol.clear();
|
|
1532
|
+
this.probingProtocols.clear();
|
|
1533
|
+
this.sessionProtocols.clear();
|
|
1534
|
+
this.protocolReprobeFailures.clear();
|
|
1535
|
+
this.monitorTokens.clear();
|
|
1536
|
+
this.protocolV2Assemblers.clear();
|
|
1537
|
+
try {
|
|
1538
|
+
manager === null || manager === void 0 ? void 0 : manager.destroy();
|
|
1539
|
+
}
|
|
1540
|
+
catch (error) {
|
|
1541
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1329
1544
|
createProtocolMismatchError(expected) {
|
|
1330
1545
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1331
1546
|
}
|
|
@@ -1333,11 +1548,19 @@ class ReactNativeBleTransport {
|
|
|
1333
1548
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
1334
1549
|
}
|
|
1335
1550
|
clearProbeProtocol(uuid, protocol) {
|
|
1551
|
+
if (this.probingProtocols.get(uuid) === protocol) {
|
|
1552
|
+
this.probingProtocols.delete(uuid);
|
|
1553
|
+
}
|
|
1336
1554
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
1337
1555
|
this.deviceProtocol.delete(uuid);
|
|
1338
1556
|
}
|
|
1339
1557
|
}
|
|
1558
|
+
getActiveProtocol(uuid) {
|
|
1559
|
+
var _a;
|
|
1560
|
+
return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
|
|
1561
|
+
}
|
|
1340
1562
|
detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
|
|
1563
|
+
var _a;
|
|
1341
1564
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1342
1565
|
if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
|
|
1343
1566
|
this.deviceProtocol.set(uuid, expectedProtocol);
|
|
@@ -1351,6 +1574,7 @@ class ReactNativeBleTransport {
|
|
|
1351
1574
|
if (expectedProtocol === 'V1') {
|
|
1352
1575
|
if (yield this.probeProtocolV1(uuid)) {
|
|
1353
1576
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1577
|
+
this.sessionProtocols.set(uuid, 'V1');
|
|
1354
1578
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1355
1579
|
deviceId: uuid,
|
|
1356
1580
|
protocol: 'V1',
|
|
@@ -1363,6 +1587,7 @@ class ReactNativeBleTransport {
|
|
|
1363
1587
|
if (expectedProtocol === 'V2') {
|
|
1364
1588
|
if (yield this.probeProtocolV2(uuid)) {
|
|
1365
1589
|
this.deviceProtocol.set(uuid, 'V2');
|
|
1590
|
+
this.sessionProtocols.set(uuid, 'V2');
|
|
1366
1591
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1367
1592
|
deviceId: uuid,
|
|
1368
1593
|
protocol: 'V2',
|
|
@@ -1372,7 +1597,13 @@ class ReactNativeBleTransport {
|
|
|
1372
1597
|
}
|
|
1373
1598
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1374
1599
|
}
|
|
1375
|
-
const
|
|
1600
|
+
const sessionProtocol = this.sessionProtocols.get(uuid);
|
|
1601
|
+
const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
1602
|
+
const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1603
|
+
const trustSessionProtocol = sessionProtocol !== undefined &&
|
|
1604
|
+
!protocolHint &&
|
|
1605
|
+
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
1606
|
+
const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
1376
1607
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1377
1608
|
const protocol = probeOrder[i];
|
|
1378
1609
|
if (i > 0) {
|
|
@@ -1387,6 +1618,8 @@ class ReactNativeBleTransport {
|
|
|
1387
1618
|
const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
|
|
1388
1619
|
if (detected) {
|
|
1389
1620
|
this.deviceProtocol.set(uuid, protocol);
|
|
1621
|
+
this.sessionProtocols.set(uuid, protocol);
|
|
1622
|
+
this.protocolReprobeFailures.delete(uuid);
|
|
1390
1623
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1391
1624
|
deviceId: uuid,
|
|
1392
1625
|
protocol,
|
|
@@ -1395,7 +1628,14 @@ class ReactNativeBleTransport {
|
|
|
1395
1628
|
return protocol;
|
|
1396
1629
|
}
|
|
1397
1630
|
}
|
|
1631
|
+
if (trustSessionProtocol) {
|
|
1632
|
+
this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
this.protocolReprobeFailures.delete(uuid);
|
|
1636
|
+
}
|
|
1398
1637
|
this.deviceProtocol.delete(uuid);
|
|
1638
|
+
this.probingProtocols.delete(uuid);
|
|
1399
1639
|
throw this.createProtocolDetectionError();
|
|
1400
1640
|
});
|
|
1401
1641
|
}
|
|
@@ -1447,13 +1687,17 @@ class ReactNativeBleTransport {
|
|
|
1447
1687
|
return false;
|
|
1448
1688
|
}
|
|
1449
1689
|
try {
|
|
1450
|
-
this.
|
|
1690
|
+
this.probingProtocols.set(uuid, 'V1');
|
|
1451
1691
|
yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1692
|
+
this.probingProtocols.delete(uuid);
|
|
1452
1693
|
return true;
|
|
1453
1694
|
}
|
|
1454
1695
|
catch (error) {
|
|
1455
1696
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1456
1697
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1698
|
+
if (isWedgedWriteError(error)) {
|
|
1699
|
+
throw error;
|
|
1700
|
+
}
|
|
1457
1701
|
return false;
|
|
1458
1702
|
}
|
|
1459
1703
|
});
|
|
@@ -1464,7 +1708,7 @@ class ReactNativeBleTransport {
|
|
|
1464
1708
|
if (!this._messages || !this._messagesV2) {
|
|
1465
1709
|
return false;
|
|
1466
1710
|
}
|
|
1467
|
-
this.
|
|
1711
|
+
this.probingProtocols.set(uuid, 'V2');
|
|
1468
1712
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1469
1713
|
const detected = yield transport.probeProtocolV2({
|
|
1470
1714
|
call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
|
|
@@ -1480,6 +1724,9 @@ class ReactNativeBleTransport {
|
|
|
1480
1724
|
if (!detected) {
|
|
1481
1725
|
this.clearProbeProtocol(uuid, 'V2');
|
|
1482
1726
|
}
|
|
1727
|
+
else {
|
|
1728
|
+
this.probingProtocols.delete(uuid);
|
|
1729
|
+
}
|
|
1483
1730
|
return detected;
|
|
1484
1731
|
});
|
|
1485
1732
|
}
|
|
@@ -1551,7 +1798,7 @@ class ReactNativeBleTransport {
|
|
|
1551
1798
|
}
|
|
1552
1799
|
});
|
|
1553
1800
|
}
|
|
1554
|
-
writeProtocolV2Packet(transport, base64, context, assertCurrentGeneration) {
|
|
1801
|
+
writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
|
|
1555
1802
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1556
1803
|
const shouldUseWriteWithResponse = transport.writeCharacteristic.isWritableWithResponse &&
|
|
1557
1804
|
(context.writeWithResponse === true || (reactNative.Platform.OS === 'ios' && !context.highVolume));
|
|
@@ -1562,12 +1809,17 @@ class ReactNativeBleTransport {
|
|
|
1562
1809
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1563
1810
|
}
|
|
1564
1811
|
try {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1812
|
+
yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
|
|
1813
|
+
? transport.writeCharacteristic.writeWithResponse(payload)
|
|
1814
|
+
: transport.writeCharacteristic.writeWithoutResponse(payload), () => {
|
|
1815
|
+
try {
|
|
1816
|
+
assertCurrentGeneration();
|
|
1817
|
+
return !context.signal.aborted;
|
|
1818
|
+
}
|
|
1819
|
+
catch (_a) {
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1571
1823
|
assertCurrentGeneration();
|
|
1572
1824
|
return;
|
|
1573
1825
|
}
|
|
@@ -1588,7 +1840,7 @@ class ReactNativeBleTransport {
|
|
|
1588
1840
|
}
|
|
1589
1841
|
});
|
|
1590
1842
|
}
|
|
1591
|
-
writeProtocolV2Frame(transport$1, frame, context, assertCurrentGeneration) {
|
|
1843
|
+
writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
|
|
1592
1844
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1593
1845
|
const tuning = getProtocolV2BleTuning();
|
|
1594
1846
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
@@ -1611,7 +1863,7 @@ class ReactNativeBleTransport {
|
|
|
1611
1863
|
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1612
1864
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1613
1865
|
wait: delay,
|
|
1614
|
-
writePacket: packet => this.writeProtocolV2Packet(transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
|
|
1866
|
+
writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
|
|
1615
1867
|
});
|
|
1616
1868
|
});
|
|
1617
1869
|
}
|
|
@@ -1660,7 +1912,7 @@ class ReactNativeBleTransport {
|
|
|
1660
1912
|
writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
|
|
1661
1913
|
assertCurrentGeneration();
|
|
1662
1914
|
const currentTransport = this.getCachedTransport(uuid);
|
|
1663
|
-
yield this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
1915
|
+
yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
|
|
1664
1916
|
}),
|
|
1665
1917
|
readFrame: () => __awaiter(this, void 0, void 0, function* () {
|
|
1666
1918
|
assertCurrentGeneration();
|
|
@@ -1681,10 +1933,16 @@ class ReactNativeBleTransport {
|
|
|
1681
1933
|
};
|
|
1682
1934
|
}
|
|
1683
1935
|
getProtocolType(path) {
|
|
1684
|
-
return this.
|
|
1936
|
+
return this.getActiveProtocol(path);
|
|
1685
1937
|
}
|
|
1686
1938
|
}
|
|
1687
1939
|
|
|
1940
|
+
exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
1941
|
+
exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
|
|
1942
|
+
exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
|
|
1943
|
+
exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
|
|
1944
|
+
exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
1945
|
+
exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
1688
1946
|
exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
|
|
1689
1947
|
exports["default"] = ReactNativeBleTransport;
|
|
1690
1948
|
exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
|
package/jest.config.js
CHANGED
|
@@ -2,4 +2,9 @@ module.exports = {
|
|
|
2
2
|
preset: '../../jest.config.js',
|
|
3
3
|
testEnvironment: 'node',
|
|
4
4
|
modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
// The workspace symlinks resolve to prebuilt dist outputs that can lag behind src.
|
|
7
|
+
'^@onekeyfe/hd-transport$': '<rootDir>/../hd-transport/src/index.ts',
|
|
8
|
+
'^@onekeyfe/hd-shared$': '<rootDir>/../shared/src/index.ts',
|
|
9
|
+
},
|
|
5
10
|
};
|