@onekeyfe/hd-transport-react-native 1.2.2-alpha.110 → 1.2.2-alpha.112
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.map +1 -1
- package/dist/index.js +64 -39
- package/package.json +5 -5
- package/src/__tests__/connectTimeout.test.ts +1 -1
- package/src/__tests__/protocolV2Link.test.ts +229 -32
- package/src/index.ts +80 -43
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAGL,UAAU,IAAI,aAAa,EAG5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAChB,KAAK,oBAAoB,EAIzB,KAAK,YAAY,EAKjB,KAAK,oBAAoB,EAI1B,MAAM,wBAAwB,CAAC;AAgChC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAI1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,KAAK,8BAA8B,GAAG,eAAe,GAAG;IAMtD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAkBF,KAAK,4BAA4B,GAAG,WAAW,CAAC;AAChD,KAAK,0BAA0B,GAAG;IAChC,mBAAmB,EAAE,cAAc,CAAC;IACpC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AAkCF,eAAO,MAAM,+BAA+B,UACnC,OAAO,KACb,4BAA4B,GAAG,IAsBjC,CAAC;AAcF,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,8BAA8B,OAAQ,CAAC;AAwBpD,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAI3D,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAiBF,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,mBAAwB,QAY5E;AAED,wBAAgB,wBAAwB,SAGvC;AAED,wBAAgB,sBAAsB;;;EAErC;AAkCD,eAAO,MAAM,sBAAsB,QAA2C,CAAC;AAO/E,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAQhD,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAEpD,eAAO,MAAM,2CAA2C,IAAI,CAAC;AAE7D,eAAO,MAAM,wBAAwB,gCAAgC,CAAC;AActE,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -420,10 +420,10 @@ function getDeviceDisplayName(device) {
|
|
|
420
420
|
}
|
|
421
421
|
const IOS_REQUEST_MTU = 247;
|
|
422
422
|
const ANDROID_REQUEST_MTU = 517;
|
|
423
|
-
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
424
423
|
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
425
424
|
const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
426
425
|
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
426
|
+
const BLE_MTU_REQUEST_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS;
|
|
427
427
|
const connectOptions = {
|
|
428
428
|
requestMTU: getRequestedBleMtu(),
|
|
429
429
|
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
@@ -457,14 +457,46 @@ const tryToGetConfiguration = (device) => {
|
|
|
457
457
|
return null;
|
|
458
458
|
return infos;
|
|
459
459
|
};
|
|
460
|
-
const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
|
|
460
|
+
const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
461
461
|
if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
|
|
462
462
|
return device;
|
|
463
|
+
const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
|
|
464
|
+
let timeoutId;
|
|
465
|
+
let timedOut = false;
|
|
463
466
|
try {
|
|
464
|
-
const
|
|
467
|
+
const request = device.requestMTU(getRequestedBleMtu(), transactionId);
|
|
468
|
+
const mtuDevice = yield Promise.race([
|
|
469
|
+
request,
|
|
470
|
+
new Promise((_, reject) => {
|
|
471
|
+
timeoutId = setTimeout(() => {
|
|
472
|
+
timedOut = true;
|
|
473
|
+
reject(new Error(`BLE MTU request timeout after ${BLE_MTU_REQUEST_TIMEOUT_MS}ms`));
|
|
474
|
+
}, BLE_MTU_REQUEST_TIMEOUT_MS);
|
|
475
|
+
}),
|
|
476
|
+
]);
|
|
465
477
|
return mtuDevice;
|
|
466
478
|
}
|
|
467
479
|
catch (error) {
|
|
480
|
+
if (timedOut && cancelTransaction) {
|
|
481
|
+
try {
|
|
482
|
+
Promise.resolve(cancelTransaction(transactionId)).catch(cancelError => {
|
|
483
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
|
|
484
|
+
platform: reactNative.Platform.OS,
|
|
485
|
+
stage,
|
|
486
|
+
attempt,
|
|
487
|
+
error: cancelError instanceof Error ? cancelError.message : String(cancelError),
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
catch (cancelError) {
|
|
492
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
|
|
493
|
+
platform: reactNative.Platform.OS,
|
|
494
|
+
stage,
|
|
495
|
+
attempt,
|
|
496
|
+
error: cancelError instanceof Error ? cancelError.message : String(cancelError),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
468
500
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
469
501
|
platform: reactNative.Platform.OS,
|
|
470
502
|
stage,
|
|
@@ -474,8 +506,14 @@ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void
|
|
|
474
506
|
});
|
|
475
507
|
return device;
|
|
476
508
|
}
|
|
509
|
+
finally {
|
|
510
|
+
if (timeoutId)
|
|
511
|
+
clearTimeout(timeoutId);
|
|
512
|
+
}
|
|
477
513
|
});
|
|
478
|
-
const resolveNegotiatedMtu = (device) =>
|
|
514
|
+
const resolveNegotiatedMtu = (device, cancelTransaction) => shouldRefreshNegotiatedMtu(device.mtu)
|
|
515
|
+
? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
|
|
516
|
+
: Promise.resolve(device);
|
|
479
517
|
function remapError(error) {
|
|
480
518
|
var _a;
|
|
481
519
|
if (error instanceof reactNativeBlePlx.BleError) {
|
|
@@ -529,11 +567,20 @@ class ReactNativeBleTransport {
|
|
|
529
567
|
},
|
|
530
568
|
classifyError: () => 'link-fatal',
|
|
531
569
|
onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
532
|
-
var _b;
|
|
570
|
+
var _b, _c;
|
|
533
571
|
(_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
|
|
534
572
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
535
573
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
536
574
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
575
|
+
if (this.probingProtocols.get(uuid) !== 'V2') {
|
|
576
|
+
const transport = transportCache[uuid];
|
|
577
|
+
try {
|
|
578
|
+
this.emitDeviceDisconnect(uuid, (_c = transport === null || transport === void 0 ? void 0 : transport.device) === null || _c === void 0 ? void 0 : _c.name, transport === null || transport === void 0 ? void 0 : transport.monitorToken);
|
|
579
|
+
}
|
|
580
|
+
catch (_d) {
|
|
581
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 disconnect listener failed');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
537
584
|
yield this.releaseNative(uuid, true);
|
|
538
585
|
}
|
|
539
586
|
}),
|
|
@@ -921,38 +968,12 @@ class ReactNativeBleTransport {
|
|
|
921
968
|
else if (reactNative.Platform.OS === 'android') {
|
|
922
969
|
yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
923
970
|
}
|
|
924
|
-
if (this.stopped)
|
|
925
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
926
|
-
const initialMtu = transport$1.mtuSize;
|
|
927
|
-
let refreshAttempts = 0;
|
|
928
|
-
if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
|
|
929
|
-
shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
|
|
930
|
-
refreshAttempts += 1;
|
|
931
|
-
let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
|
|
932
|
-
if (this.stopped)
|
|
933
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
934
|
-
transport$1.device = refreshedDevice;
|
|
935
|
-
transport$1.mtuSize =
|
|
936
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
|
|
937
|
-
if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
|
|
938
|
-
yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
939
|
-
if (this.stopped)
|
|
940
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
941
|
-
refreshAttempts += 1;
|
|
942
|
-
refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
|
|
943
|
-
transport$1.device = refreshedDevice;
|
|
944
|
-
transport$1.mtuSize =
|
|
945
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
971
|
if (this.stopped)
|
|
949
972
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
950
973
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
951
974
|
platform: reactNative.Platform.OS,
|
|
952
975
|
requested: getRequestedBleMtu(),
|
|
953
|
-
initial: initialMtu,
|
|
954
976
|
actual: transport$1.mtuSize,
|
|
955
|
-
refreshAttempts,
|
|
956
977
|
});
|
|
957
978
|
return transport$1;
|
|
958
979
|
});
|
|
@@ -1136,7 +1157,7 @@ class ReactNativeBleTransport {
|
|
|
1136
1157
|
}
|
|
1137
1158
|
if (this.stopped)
|
|
1138
1159
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
1139
|
-
device = yield resolveNegotiatedMtu(device);
|
|
1160
|
+
device = yield resolveNegotiatedMtu(device, transactionId => blePlxManager.cancelTransaction(transactionId));
|
|
1140
1161
|
if (this.stopped)
|
|
1141
1162
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
1142
1163
|
const acquiredDevice = device;
|
|
@@ -1195,12 +1216,7 @@ class ReactNativeBleTransport {
|
|
|
1195
1216
|
return { uuid, protocolType };
|
|
1196
1217
|
}
|
|
1197
1218
|
catch (error) {
|
|
1198
|
-
|
|
1199
|
-
yield this.disconnectUnlocked(uuid);
|
|
1200
|
-
}
|
|
1201
|
-
else {
|
|
1202
|
-
yield this.releaseUnlocked(uuid, true);
|
|
1203
|
-
}
|
|
1219
|
+
yield this.disconnectUnlocked(uuid);
|
|
1204
1220
|
throw error;
|
|
1205
1221
|
}
|
|
1206
1222
|
finally {
|
|
@@ -2378,6 +2394,7 @@ class ReactNativeBleTransport {
|
|
|
2378
2394
|
if (!this._messages || !this._messagesV2) {
|
|
2379
2395
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
2380
2396
|
}
|
|
2397
|
+
const isProtocolProbe = this.probingProtocols.get(uuid) === 'V2';
|
|
2381
2398
|
const callOptions = options;
|
|
2382
2399
|
const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
|
|
2383
2400
|
if (highThroughputWrite) {
|
|
@@ -2418,6 +2435,14 @@ class ReactNativeBleTransport {
|
|
|
2418
2435
|
}
|
|
2419
2436
|
catch (e) {
|
|
2420
2437
|
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
2438
|
+
if (!isProtocolProbe &&
|
|
2439
|
+
(e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
|
|
2440
|
+
!this.monitorTokens.has(uuid)) {
|
|
2441
|
+
yield this.runLifecycleOperation(uuid, () => __awaiter(this, void 0, void 0, function* () {
|
|
2442
|
+
if (!this.monitorTokens.has(uuid))
|
|
2443
|
+
yield this.disconnectUnlocked(uuid);
|
|
2444
|
+
}));
|
|
2445
|
+
}
|
|
2421
2446
|
throw e;
|
|
2422
2447
|
}
|
|
2423
2448
|
finally {
|
|
@@ -2432,7 +2457,7 @@ class ReactNativeBleTransport {
|
|
|
2432
2457
|
const transport = this.getCachedTransport(uuid);
|
|
2433
2458
|
if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
|
|
2434
2459
|
return;
|
|
2435
|
-
const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
|
|
2460
|
+
const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1, transactionId => { var _a; return (_a = this.blePlxManager) === null || _a === void 0 ? void 0 : _a.cancelTransaction(transactionId); });
|
|
2436
2461
|
transport.device = refreshedDevice;
|
|
2437
2462
|
transport.mtuSize =
|
|
2438
2463
|
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.112",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-core": "1.2.2-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.2-alpha.112",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.112",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.112",
|
|
26
26
|
"@onekeyfe/react-native-ble-utils": "0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "3b31cf365ea4042a5fb2e78dd7ede9a2d9d96e04"
|
|
30
30
|
}
|
|
@@ -446,7 +446,7 @@ describe('BLE connect timeout', () => {
|
|
|
446
446
|
const nativeOperation = createDeferred<typeof device>();
|
|
447
447
|
const nativeDisconnect = createDeferred<void>();
|
|
448
448
|
const requestMtu = jest.fn(() => Promise.resolve(device));
|
|
449
|
-
Object.assign(device, { mtu: 247, requestMTU: requestMtu });
|
|
449
|
+
Object.assign(device, { mtu: stage === 'mtu' ? 23 : 247, requestMTU: requestMtu });
|
|
450
450
|
device.isConnected.mockResolvedValue(true);
|
|
451
451
|
if (stage === 'connect') device.isConnected.mockResolvedValueOnce(false);
|
|
452
452
|
if (stage === 'mtu') requestMtu.mockImplementationOnce(() => nativeOperation.promise);
|
|
@@ -615,7 +615,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
615
615
|
uuid,
|
|
616
616
|
protocolType: 'V2',
|
|
617
617
|
});
|
|
618
|
-
expect(device.requestMTU).
|
|
618
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
619
619
|
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
620
620
|
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
|
|
621
621
|
|
|
@@ -714,7 +714,72 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
714
714
|
});
|
|
715
715
|
|
|
716
716
|
test.each(['ios', 'android'] as const)(
|
|
717
|
-
'
|
|
717
|
+
'waits for native disconnect after failed protocol detection before reconnecting on %s',
|
|
718
|
+
async platform => {
|
|
719
|
+
setPlatformOS(platform);
|
|
720
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
721
|
+
const probes = transport as unknown as {
|
|
722
|
+
probeProtocolV1(uuid: string): Promise<boolean>;
|
|
723
|
+
probeProtocolV2(uuid: string): Promise<boolean>;
|
|
724
|
+
releaseNative(uuid: string, onclose: boolean): Promise<void>;
|
|
725
|
+
};
|
|
726
|
+
jest.spyOn(probes, 'probeProtocolV1').mockResolvedValueOnce(false);
|
|
727
|
+
const probesFinished = createDeferred<void>();
|
|
728
|
+
const probeProtocolV2 = jest
|
|
729
|
+
.spyOn(probes, 'probeProtocolV2')
|
|
730
|
+
.mockImplementationOnce(async () => {
|
|
731
|
+
// A link-fatal V2 timeout removes the cached transport before acquire fails.
|
|
732
|
+
await probes.releaseNative(uuid, true);
|
|
733
|
+
probesFinished.resolve();
|
|
734
|
+
return false;
|
|
735
|
+
});
|
|
736
|
+
let connected = true;
|
|
737
|
+
device.isConnected.mockImplementation(() => Promise.resolve(connected));
|
|
738
|
+
device.connect = jest.fn(() => {
|
|
739
|
+
connected = true;
|
|
740
|
+
return Promise.resolve(device);
|
|
741
|
+
});
|
|
742
|
+
const disconnectGate = createDeferred<void>();
|
|
743
|
+
bleManager.cancelDeviceConnection.mockImplementation(async () => {
|
|
744
|
+
await disconnectGate.promise;
|
|
745
|
+
connected = false;
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
const acquiring = transport.acquire({ uuid });
|
|
749
|
+
const failure = expect(acquiring).rejects.toMatchObject({
|
|
750
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
751
|
+
});
|
|
752
|
+
const reconnecting = transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
await probesFinished.promise;
|
|
756
|
+
await new Promise(resolve => {
|
|
757
|
+
setImmediate(resolve);
|
|
758
|
+
});
|
|
759
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
760
|
+
expect(device.connect).not.toHaveBeenCalled();
|
|
761
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
762
|
+
|
|
763
|
+
disconnectGate.resolve();
|
|
764
|
+
await failure;
|
|
765
|
+
await expect(reconnecting).resolves.toEqual({ uuid, protocolType: 'V2' });
|
|
766
|
+
expect(device.connect).toHaveBeenCalledTimes(1);
|
|
767
|
+
await expect(
|
|
768
|
+
transport.call(uuid, 'Ping', { message: 'after-reconnect' })
|
|
769
|
+
).resolves.toMatchObject({
|
|
770
|
+
type: 'Success',
|
|
771
|
+
message: { message: 'ok' },
|
|
772
|
+
});
|
|
773
|
+
} finally {
|
|
774
|
+
disconnectGate.resolve();
|
|
775
|
+
await Promise.allSettled([failure, reconnecting]);
|
|
776
|
+
await transport.release(uuid, true);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
test.each(['ios', 'android'] as const)(
|
|
782
|
+
'disconnects after a first expected Protocol V2 probe miss while keeping it retryable on %s',
|
|
718
783
|
async platform => {
|
|
719
784
|
setPlatformOS(platform);
|
|
720
785
|
const { transport, uuid, device } = createHarness();
|
|
@@ -724,13 +789,13 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
724
789
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
725
790
|
});
|
|
726
791
|
|
|
727
|
-
expect(device.cancelConnection).
|
|
792
|
+
expect(device.cancelConnection).toHaveBeenCalledTimes(1);
|
|
728
793
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
729
794
|
}
|
|
730
795
|
);
|
|
731
796
|
|
|
732
797
|
test.each(['ios', 'android'] as const)(
|
|
733
|
-
'
|
|
798
|
+
'disconnects after each expected Protocol V2 probe miss while keeping it retryable on %s',
|
|
734
799
|
async platform => {
|
|
735
800
|
setPlatformOS(platform);
|
|
736
801
|
const { transport, uuid, device } = createHarness();
|
|
@@ -743,7 +808,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
743
808
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
744
809
|
});
|
|
745
810
|
|
|
746
|
-
expect(device.cancelConnection).
|
|
811
|
+
expect(device.cancelConnection).toHaveBeenCalledTimes(2);
|
|
747
812
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
748
813
|
}
|
|
749
814
|
);
|
|
@@ -778,7 +843,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
778
843
|
);
|
|
779
844
|
|
|
780
845
|
test.each(['ios', 'android'] as const)(
|
|
781
|
-
'
|
|
846
|
+
'disconnects after a confirmed Protocol V2 probe miss on %s without reporting a bond error',
|
|
782
847
|
async platform => {
|
|
783
848
|
setPlatformOS(platform);
|
|
784
849
|
const { transport, uuid, device } = createHarness();
|
|
@@ -790,7 +855,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
790
855
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
791
856
|
});
|
|
792
857
|
|
|
793
|
-
expect(device.cancelConnection).
|
|
858
|
+
expect(device.cancelConnection).toHaveBeenCalledTimes(1);
|
|
794
859
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
795
860
|
}
|
|
796
861
|
);
|
|
@@ -974,7 +1039,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
974
1039
|
test.each(['V1', 'V2'] as const)(
|
|
975
1040
|
'still falls back after a silent %s probe timeout',
|
|
976
1041
|
async protocol => {
|
|
977
|
-
const { transport, uuid } = createHarness();
|
|
1042
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
978
1043
|
jest
|
|
979
1044
|
.spyOn(transport as any, protocol === 'V1' ? 'callProtocolV1' : 'callProtocolV2')
|
|
980
1045
|
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError));
|
|
@@ -988,6 +1053,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
988
1053
|
});
|
|
989
1054
|
|
|
990
1055
|
expect(otherProbe).toHaveBeenCalledTimes(1);
|
|
1056
|
+
expect(bleManager.cancelDeviceConnection).not.toHaveBeenCalled();
|
|
1057
|
+
expect(device.cancelConnection).not.toHaveBeenCalled();
|
|
991
1058
|
await transport.release(uuid, true);
|
|
992
1059
|
}
|
|
993
1060
|
);
|
|
@@ -1012,57 +1079,69 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1012
1079
|
await transport.release(uuid, true);
|
|
1013
1080
|
});
|
|
1014
1081
|
|
|
1015
|
-
test('
|
|
1082
|
+
test('does not renegotiate an already usable MTU during acquire', async () => {
|
|
1016
1083
|
const { transport, uuid, device } = createHarness();
|
|
1017
|
-
const mtuError = new Error('MTU refresh failed');
|
|
1018
|
-
device.requestMTU.mockRejectedValueOnce(mtuError);
|
|
1019
1084
|
|
|
1020
1085
|
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1021
1086
|
uuid,
|
|
1022
1087
|
protocolType: 'V2',
|
|
1023
1088
|
});
|
|
1024
|
-
expect(device.requestMTU).
|
|
1089
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1025
1090
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
|
|
1026
1091
|
await transport.release(uuid, true);
|
|
1027
1092
|
});
|
|
1028
1093
|
|
|
1029
|
-
test('
|
|
1094
|
+
test('requests a low MTU once before protocol probing', async () => {
|
|
1030
1095
|
const { transport, uuid, device } = createHarness();
|
|
1031
1096
|
device.mtu = 23;
|
|
1032
|
-
device.requestMTU
|
|
1033
|
-
.
|
|
1034
|
-
.
|
|
1035
|
-
|
|
1036
|
-
device.mtu = 247;
|
|
1037
|
-
return Promise.resolve(device);
|
|
1038
|
-
});
|
|
1097
|
+
device.requestMTU.mockImplementationOnce(() => {
|
|
1098
|
+
device.mtu = 247;
|
|
1099
|
+
return Promise.resolve(device);
|
|
1100
|
+
});
|
|
1039
1101
|
|
|
1040
1102
|
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1041
1103
|
uuid,
|
|
1042
1104
|
protocolType: 'V2',
|
|
1043
1105
|
});
|
|
1044
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(
|
|
1106
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
1107
|
+
expect(device.requestMTU).toHaveBeenCalledWith(
|
|
1108
|
+
247,
|
|
1109
|
+
expect.stringContaining(`${uuid}:mtu:connected:0:`)
|
|
1110
|
+
);
|
|
1045
1111
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
|
|
1046
1112
|
await transport.release(uuid, true);
|
|
1047
1113
|
});
|
|
1048
1114
|
|
|
1049
|
-
test('continues with a low
|
|
1115
|
+
test('continues with a low MTU when the single negotiation fails', async () => {
|
|
1050
1116
|
const { transport, uuid, device } = createHarness();
|
|
1051
1117
|
device.mtu = 23;
|
|
1052
|
-
device.requestMTU
|
|
1053
|
-
.mockResolvedValueOnce(device)
|
|
1054
|
-
.mockResolvedValueOnce(device)
|
|
1055
|
-
.mockRejectedValueOnce(new Error('bootloader MTU retry failed'));
|
|
1118
|
+
device.requestMTU.mockRejectedValueOnce(new Error('MTU negotiation failed'));
|
|
1056
1119
|
|
|
1057
1120
|
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1058
1121
|
uuid,
|
|
1059
1122
|
protocolType: 'V2',
|
|
1060
1123
|
});
|
|
1061
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(
|
|
1124
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
1062
1125
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
1063
1126
|
await transport.release(uuid, true);
|
|
1064
1127
|
});
|
|
1065
1128
|
|
|
1129
|
+
test('bounds a stalled MTU negotiation and continues protocol probing', async () => {
|
|
1130
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
1131
|
+
device.mtu = 23;
|
|
1132
|
+
device.requestMTU.mockImplementationOnce(() => new Promise(() => {}));
|
|
1133
|
+
|
|
1134
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1135
|
+
uuid,
|
|
1136
|
+
protocolType: 'V2',
|
|
1137
|
+
});
|
|
1138
|
+
const transactionId = device.requestMTU.mock.calls[0]?.[1];
|
|
1139
|
+
expect(transactionId).toEqual(expect.stringContaining(`${uuid}:mtu:connected:0:`));
|
|
1140
|
+
expect(bleManager.cancelTransaction).toHaveBeenCalledWith(transactionId);
|
|
1141
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
1142
|
+
await transport.release(uuid, true);
|
|
1143
|
+
}, 10_000);
|
|
1144
|
+
|
|
1066
1145
|
test('accepts a stable low MTU without the delayed refresh loop', async () => {
|
|
1067
1146
|
const { transport, uuid, device } = createHarness();
|
|
1068
1147
|
device.mtu = 185;
|
|
@@ -1071,7 +1150,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1071
1150
|
uuid,
|
|
1072
1151
|
protocolType: 'V2',
|
|
1073
1152
|
});
|
|
1074
|
-
expect(device.requestMTU).
|
|
1153
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1075
1154
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(185);
|
|
1076
1155
|
await transport.release(uuid, true);
|
|
1077
1156
|
});
|
|
@@ -1084,7 +1163,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1084
1163
|
uuid,
|
|
1085
1164
|
protocolType: 'V2',
|
|
1086
1165
|
});
|
|
1087
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(
|
|
1166
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
1088
1167
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBeUndefined();
|
|
1089
1168
|
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
|
|
1090
1169
|
await transport.release(uuid, true);
|
|
@@ -1102,7 +1181,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1102
1181
|
});
|
|
1103
1182
|
|
|
1104
1183
|
await expect(transport.call(uuid, 'FileWrite', {})).resolves.toBeDefined();
|
|
1105
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(
|
|
1184
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(2);
|
|
1106
1185
|
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
1107
1186
|
writesBeforeFileWrite + 1
|
|
1108
1187
|
);
|
|
@@ -1119,7 +1198,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1119
1198
|
await expect(transport.call(uuid, 'FileWrite', {})).rejects.toMatchObject({
|
|
1120
1199
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1121
1200
|
});
|
|
1122
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(
|
|
1201
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(2);
|
|
1123
1202
|
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(writesBeforeFileWrite);
|
|
1124
1203
|
await transport.release(uuid, true);
|
|
1125
1204
|
});
|
|
@@ -1173,6 +1252,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1173
1252
|
expect(notifySubscriptionRemovers[0]).toHaveBeenCalledTimes(1);
|
|
1174
1253
|
expect(notifySubscriptionRemovers[1]).toHaveBeenCalledTimes(1);
|
|
1175
1254
|
expect(bleManager.cancelTransaction).toHaveBeenCalled();
|
|
1255
|
+
expect(device.cancelConnection).toHaveBeenCalledTimes(1);
|
|
1176
1256
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
1177
1257
|
});
|
|
1178
1258
|
|
|
@@ -1227,6 +1307,115 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1227
1307
|
});
|
|
1228
1308
|
});
|
|
1229
1309
|
|
|
1310
|
+
test.each(['ios', 'android'] as const)(
|
|
1311
|
+
'disconnects after an active V2 timeout and reconnects even if a listener fails on %s',
|
|
1312
|
+
async platform => {
|
|
1313
|
+
setPlatformOS(platform);
|
|
1314
|
+
const harness = createHarness();
|
|
1315
|
+
const { transport, uuid, device, bleManager, sentSeqs, emitter } = harness;
|
|
1316
|
+
const disconnected = jest.fn(() => {
|
|
1317
|
+
throw new Error('Disconnect listener failed');
|
|
1318
|
+
});
|
|
1319
|
+
emitter.on(TRANSPORT_EVENT.DEVICE_DISCONNECT, disconnected);
|
|
1320
|
+
let connected = true;
|
|
1321
|
+
device.isConnected.mockImplementation(() => Promise.resolve(connected));
|
|
1322
|
+
device.connect = jest.fn(() => {
|
|
1323
|
+
connected = true;
|
|
1324
|
+
return Promise.resolve(device);
|
|
1325
|
+
});
|
|
1326
|
+
bleManager.cancelDeviceConnection.mockImplementation(() => {
|
|
1327
|
+
connected = false;
|
|
1328
|
+
return Promise.resolve();
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1332
|
+
harness.setShouldRespond(false);
|
|
1333
|
+
await expect(transport.call(uuid, 'Ping', {}, { timeoutMs: 10 })).rejects.toMatchObject({
|
|
1334
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1338
|
+
expect(connected).toBe(false);
|
|
1339
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
1340
|
+
expect(disconnected).toHaveBeenCalledTimes(1);
|
|
1341
|
+
expect(disconnected).toHaveBeenCalledWith({
|
|
1342
|
+
id: uuid,
|
|
1343
|
+
connectId: uuid,
|
|
1344
|
+
name: device.name,
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
harness.setShouldRespond(true);
|
|
1348
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1349
|
+
await expect(transport.call(uuid, 'Ping', {})).resolves.toMatchObject({ type: 'Success' });
|
|
1350
|
+
expect(device.connect).toHaveBeenCalledTimes(1);
|
|
1351
|
+
expect(sentSeqs).toEqual([1, 2, 3, 4]);
|
|
1352
|
+
await transport.release(uuid, true);
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
|
|
1356
|
+
test('does not disconnect an active Protocol V2 call when a queued call times out', async () => {
|
|
1357
|
+
const harness = createHarness();
|
|
1358
|
+
const { transport, uuid, bleManager } = harness;
|
|
1359
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1360
|
+
harness.setShouldRespond(false);
|
|
1361
|
+
const active = transport.call(uuid, 'Ping', {}, { timeoutMs: 1000 }).catch(error => error);
|
|
1362
|
+
await new Promise(resolve => {
|
|
1363
|
+
setImmediate(resolve);
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
try {
|
|
1367
|
+
await expect(transport.call(uuid, 'Ping', {}, { timeoutMs: 10 })).rejects.toMatchObject({
|
|
1368
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
1369
|
+
});
|
|
1370
|
+
expect(bleManager.cancelDeviceConnection).not.toHaveBeenCalled();
|
|
1371
|
+
expect(transport.getProtocolType(uuid)).toBe('V2');
|
|
1372
|
+
} finally {
|
|
1373
|
+
await transport.disconnect(uuid);
|
|
1374
|
+
await active;
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
test('does not disconnect a newer acquire when V2 timeout cleanup waits for the lifecycle lock', async () => {
|
|
1379
|
+
const harness = createHarness();
|
|
1380
|
+
const { transport, uuid, bleManager } = harness;
|
|
1381
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1382
|
+
const lifecycle = transport as unknown as {
|
|
1383
|
+
runLifecycleOperation(id: string, operation: () => Promise<void>): Promise<void>;
|
|
1384
|
+
releaseNative(id: string, onclose: boolean): Promise<boolean>;
|
|
1385
|
+
};
|
|
1386
|
+
const gate = createDeferred<void>();
|
|
1387
|
+
const holding = lifecycle.runLifecycleOperation(uuid, () => gate.promise);
|
|
1388
|
+
const reacquiring = transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1389
|
+
const invalidated = createDeferred<void>();
|
|
1390
|
+
const releaseNative = lifecycle.releaseNative.bind(lifecycle);
|
|
1391
|
+
jest.spyOn(lifecycle, 'releaseNative').mockImplementationOnce(async (id, onclose) => {
|
|
1392
|
+
const result = await releaseNative(id, onclose);
|
|
1393
|
+
invalidated.resolve();
|
|
1394
|
+
return result;
|
|
1395
|
+
});
|
|
1396
|
+
harness.setShouldRespond(false);
|
|
1397
|
+
const failure = expect(
|
|
1398
|
+
transport.call(uuid, 'Ping', {}, { timeoutMs: 10 })
|
|
1399
|
+
).rejects.toMatchObject({
|
|
1400
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
try {
|
|
1404
|
+
await invalidated.promise;
|
|
1405
|
+
harness.setShouldRespond(true);
|
|
1406
|
+
gate.resolve();
|
|
1407
|
+
await holding;
|
|
1408
|
+
await reacquiring;
|
|
1409
|
+
await failure;
|
|
1410
|
+
expect(bleManager.cancelDeviceConnection).not.toHaveBeenCalled();
|
|
1411
|
+
await expect(transport.call(uuid, 'Ping', {})).resolves.toMatchObject({ type: 'Success' });
|
|
1412
|
+
} finally {
|
|
1413
|
+
gate.resolve();
|
|
1414
|
+
await Promise.allSettled([holding, reacquiring, failure]);
|
|
1415
|
+
await transport.release(uuid, true);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1230
1419
|
test('retains the sequence cursor when a new monitor generation is acquired', async () => {
|
|
1231
1420
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
1232
1421
|
|
|
@@ -1279,9 +1468,17 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1279
1468
|
test('uses Android 517 MTU and high connection priority during Protocol V2 high-volume calls', async () => {
|
|
1280
1469
|
setPlatformOS('android');
|
|
1281
1470
|
const { transport, uuid, device } = createHarness();
|
|
1471
|
+
device.mtu = 23;
|
|
1472
|
+
device.requestMTU.mockImplementationOnce(() => {
|
|
1473
|
+
device.mtu = 517;
|
|
1474
|
+
return Promise.resolve(device);
|
|
1475
|
+
});
|
|
1282
1476
|
|
|
1283
1477
|
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1284
|
-
expect(device.requestMTU).toHaveBeenCalledWith(
|
|
1478
|
+
expect(device.requestMTU).toHaveBeenCalledWith(
|
|
1479
|
+
517,
|
|
1480
|
+
expect.stringContaining(`${uuid}:mtu:connected:0:`)
|
|
1481
|
+
);
|
|
1285
1482
|
|
|
1286
1483
|
await transport.call(uuid, 'FileWrite', {});
|
|
1287
1484
|
await transport.call(uuid, 'FileWrite', {});
|
package/src/index.ts
CHANGED
|
@@ -242,13 +242,13 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
242
242
|
|
|
243
243
|
const IOS_REQUEST_MTU = 247;
|
|
244
244
|
const ANDROID_REQUEST_MTU = 517;
|
|
245
|
-
const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
|
|
246
245
|
const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
|
|
247
246
|
|
|
248
247
|
const getRequestedBleMtu = () =>
|
|
249
248
|
Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
|
|
250
249
|
|
|
251
250
|
const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
|
|
251
|
+
const BLE_MTU_REQUEST_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS;
|
|
252
252
|
|
|
253
253
|
const connectOptions: Record<string, unknown> = {
|
|
254
254
|
requestMTU: getRequestedBleMtu(),
|
|
@@ -314,17 +314,50 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
314
314
|
|
|
315
315
|
const requestNegotiatedMtu = async (
|
|
316
316
|
device: Device,
|
|
317
|
-
stage: 'connected' | '
|
|
318
|
-
attempt: number
|
|
317
|
+
stage: 'connected' | 'highThroughput',
|
|
318
|
+
attempt: number,
|
|
319
|
+
cancelTransaction?: (transactionId: string) => Promise<void> | void
|
|
319
320
|
) => {
|
|
320
321
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
321
322
|
|
|
323
|
+
const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
|
|
324
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
325
|
+
let timedOut = false;
|
|
326
|
+
|
|
322
327
|
try {
|
|
323
328
|
// iOS ignores the requested value but react-native-ble-plx returns a fresh
|
|
324
329
|
// Device snapshot whose MTU is derived from CoreBluetooth's maximum write length.
|
|
325
|
-
const
|
|
330
|
+
const request = device.requestMTU(getRequestedBleMtu(), transactionId);
|
|
331
|
+
const mtuDevice = await Promise.race([
|
|
332
|
+
request,
|
|
333
|
+
new Promise<never>((_, reject) => {
|
|
334
|
+
timeoutId = setTimeout(() => {
|
|
335
|
+
timedOut = true;
|
|
336
|
+
reject(new Error(`BLE MTU request timeout after ${BLE_MTU_REQUEST_TIMEOUT_MS}ms`));
|
|
337
|
+
}, BLE_MTU_REQUEST_TIMEOUT_MS);
|
|
338
|
+
}),
|
|
339
|
+
]);
|
|
326
340
|
return mtuDevice;
|
|
327
341
|
} catch (error) {
|
|
342
|
+
if (timedOut && cancelTransaction) {
|
|
343
|
+
try {
|
|
344
|
+
Promise.resolve(cancelTransaction(transactionId)).catch(cancelError => {
|
|
345
|
+
Log?.debug('[ReactNativeBleTransport] MTU cancellation failed', {
|
|
346
|
+
platform: Platform.OS,
|
|
347
|
+
stage,
|
|
348
|
+
attempt,
|
|
349
|
+
error: cancelError instanceof Error ? cancelError.message : String(cancelError),
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
} catch (cancelError) {
|
|
353
|
+
Log?.debug('[ReactNativeBleTransport] MTU cancellation failed', {
|
|
354
|
+
platform: Platform.OS,
|
|
355
|
+
stage,
|
|
356
|
+
attempt,
|
|
357
|
+
error: cancelError instanceof Error ? cancelError.message : String(cancelError),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
328
361
|
Log?.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
|
|
329
362
|
platform: Platform.OS,
|
|
330
363
|
stage,
|
|
@@ -333,10 +366,18 @@ const requestNegotiatedMtu = async (
|
|
|
333
366
|
error: error instanceof Error ? error.message : String(error),
|
|
334
367
|
});
|
|
335
368
|
return device;
|
|
369
|
+
} finally {
|
|
370
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
336
371
|
}
|
|
337
372
|
};
|
|
338
373
|
|
|
339
|
-
const resolveNegotiatedMtu = (
|
|
374
|
+
const resolveNegotiatedMtu = (
|
|
375
|
+
device: Device,
|
|
376
|
+
cancelTransaction?: (transactionId: string) => Promise<void> | void
|
|
377
|
+
) =>
|
|
378
|
+
shouldRefreshNegotiatedMtu(device.mtu)
|
|
379
|
+
? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
|
|
380
|
+
: Promise.resolve(device);
|
|
340
381
|
|
|
341
382
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
342
383
|
|
|
@@ -452,6 +493,14 @@ export default class ReactNativeBleTransport {
|
|
|
452
493
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
453
494
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
454
495
|
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
496
|
+
if (this.probingProtocols.get(uuid) !== 'V2') {
|
|
497
|
+
const transport = transportCache[uuid];
|
|
498
|
+
try {
|
|
499
|
+
this.emitDeviceDisconnect(uuid, transport?.device?.name, transport?.monitorToken);
|
|
500
|
+
} catch {
|
|
501
|
+
Log?.error('[ReactNativeBleTransport] Protocol V2 disconnect listener failed');
|
|
502
|
+
}
|
|
503
|
+
}
|
|
455
504
|
await this.releaseNative(uuid, true);
|
|
456
505
|
}
|
|
457
506
|
},
|
|
@@ -918,41 +967,10 @@ export default class ReactNativeBleTransport {
|
|
|
918
967
|
}
|
|
919
968
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
920
969
|
|
|
921
|
-
const initialMtu = transport.mtuSize;
|
|
922
|
-
let refreshAttempts = 0;
|
|
923
|
-
if (
|
|
924
|
-
(Platform.OS === 'ios' || Platform.OS === 'android') &&
|
|
925
|
-
shouldRefreshNegotiatedMtu(transport.mtuSize)
|
|
926
|
-
) {
|
|
927
|
-
refreshAttempts += 1;
|
|
928
|
-
let refreshedDevice = await requestNegotiatedMtu(
|
|
929
|
-
transport.device,
|
|
930
|
-
'servicesAndNotifyReady',
|
|
931
|
-
1
|
|
932
|
-
);
|
|
933
|
-
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
934
|
-
transport.device = refreshedDevice;
|
|
935
|
-
transport.mtuSize =
|
|
936
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
937
|
-
|
|
938
|
-
if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
|
|
939
|
-
await delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
|
|
940
|
-
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
941
|
-
refreshAttempts += 1;
|
|
942
|
-
refreshedDevice = await requestNegotiatedMtu(transport.device, 'servicesAndNotifyReady', 2);
|
|
943
|
-
transport.device = refreshedDevice;
|
|
944
|
-
transport.mtuSize =
|
|
945
|
-
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
950
970
|
Log?.debug('[ReactNativeBleTransport] BLE MTU ready', {
|
|
951
971
|
platform: Platform.OS,
|
|
952
972
|
requested: getRequestedBleMtu(),
|
|
953
|
-
initial: initialMtu,
|
|
954
973
|
actual: transport.mtuSize,
|
|
955
|
-
refreshAttempts,
|
|
956
974
|
});
|
|
957
975
|
|
|
958
976
|
return transport;
|
|
@@ -1180,7 +1198,9 @@ export default class ReactNativeBleTransport {
|
|
|
1180
1198
|
throw ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'device is not connected');
|
|
1181
1199
|
}
|
|
1182
1200
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1183
|
-
device = await resolveNegotiatedMtu(device
|
|
1201
|
+
device = await resolveNegotiatedMtu(device, transactionId =>
|
|
1202
|
+
blePlxManager.cancelTransaction(transactionId)
|
|
1203
|
+
);
|
|
1184
1204
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1185
1205
|
const acquiredDevice = device;
|
|
1186
1206
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
@@ -1255,11 +1275,9 @@ export default class ReactNativeBleTransport {
|
|
|
1255
1275
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1256
1276
|
return { uuid, protocolType };
|
|
1257
1277
|
} catch (error) {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
await this.releaseUnlocked(uuid, true);
|
|
1262
|
-
}
|
|
1278
|
+
// A failed acquire must retire the physical link before Core retries. Logical
|
|
1279
|
+
// release leaves GATT connected even when neither protocol receives a response.
|
|
1280
|
+
await this.disconnectUnlocked(uuid);
|
|
1263
1281
|
throw error;
|
|
1264
1282
|
} finally {
|
|
1265
1283
|
this.acquiringProtocolV2.delete(uuid);
|
|
@@ -2738,6 +2756,7 @@ export default class ReactNativeBleTransport {
|
|
|
2738
2756
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
2739
2757
|
}
|
|
2740
2758
|
|
|
2759
|
+
const isProtocolProbe = this.probingProtocols.get(uuid) === 'V2';
|
|
2741
2760
|
const callOptions = options;
|
|
2742
2761
|
const highThroughputWrite = isProtocolV2HighThroughputCall(name);
|
|
2743
2762
|
|
|
@@ -2789,6 +2808,19 @@ export default class ReactNativeBleTransport {
|
|
|
2789
2808
|
);
|
|
2790
2809
|
} catch (e) {
|
|
2791
2810
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
2811
|
+
if (
|
|
2812
|
+
!isProtocolProbe &&
|
|
2813
|
+
e?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
2814
|
+
!this.monitorTokens.has(uuid)
|
|
2815
|
+
) {
|
|
2816
|
+
// The failed link has finished invalidating. Disconnect outside that
|
|
2817
|
+
// callback to avoid waiting on its own invalidation or acquire lock.
|
|
2818
|
+
await this.runLifecycleOperation(uuid, async () => {
|
|
2819
|
+
// A queued timeout leaves its active monitor intact; a newer acquire
|
|
2820
|
+
// may also have installed one while cleanup waited for the lifecycle lock.
|
|
2821
|
+
if (!this.monitorTokens.has(uuid)) await this.disconnectUnlocked(uuid);
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2792
2824
|
throw e;
|
|
2793
2825
|
} finally {
|
|
2794
2826
|
if (highThroughputWrite) {
|
|
@@ -2801,7 +2833,12 @@ export default class ReactNativeBleTransport {
|
|
|
2801
2833
|
const transport = this.getCachedTransport(uuid);
|
|
2802
2834
|
if (!shouldRefreshNegotiatedMtu(transport.mtuSize)) return;
|
|
2803
2835
|
|
|
2804
|
-
const refreshedDevice = await requestNegotiatedMtu(
|
|
2836
|
+
const refreshedDevice = await requestNegotiatedMtu(
|
|
2837
|
+
transport.device,
|
|
2838
|
+
'highThroughput',
|
|
2839
|
+
1,
|
|
2840
|
+
transactionId => this.blePlxManager?.cancelTransaction(transactionId)
|
|
2841
|
+
);
|
|
2805
2842
|
transport.device = refreshedDevice;
|
|
2806
2843
|
transport.mtuSize =
|
|
2807
2844
|
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|