@onekeyfe/hd-transport-react-native 1.2.2-alpha.116 → 1.2.2-alpha.117
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 +13 -59
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -179
- package/package.json +5 -5
- package/src/__tests__/connectTimeout.test.ts +18 -38
- package/src/__tests__/protocolV2Link.test.ts +255 -298
- package/src/index.ts +135 -330
|
@@ -111,18 +111,10 @@ const createHarness = ({
|
|
|
111
111
|
deviceName = 'OneKey Pro 2',
|
|
112
112
|
isWritableWithResponse = true,
|
|
113
113
|
monitorError,
|
|
114
|
-
pro2BleReplies = false,
|
|
115
114
|
}: {
|
|
116
115
|
deviceName?: string;
|
|
117
116
|
isWritableWithResponse?: boolean;
|
|
118
117
|
monitorError?: (Error & { reason?: string; attErrorCode?: number; iosErrorCode?: number }) | null;
|
|
119
|
-
/**
|
|
120
|
-
* Answer like a real Pro 2 over BLE: reassemble a frame split across ATT writes, echo the
|
|
121
|
-
* 17-character probe message so the reply is 29 bytes, and send it as one notification of
|
|
122
|
-
* at most ATT_MTU-3 bytes. A reply that does not fit is cut, never continued (btsnoop:
|
|
123
|
-
* 20 bytes declaring 29 at the default MTU).
|
|
124
|
-
*/
|
|
125
|
-
pro2BleReplies?: boolean;
|
|
126
118
|
} = {}) => {
|
|
127
119
|
const uuid = 'rn-pro2-id';
|
|
128
120
|
const sentSeqs: number[] = [];
|
|
@@ -147,31 +139,18 @@ const createHarness = ({
|
|
|
147
139
|
return { remove: jest.fn() };
|
|
148
140
|
}),
|
|
149
141
|
};
|
|
150
|
-
let pendingPro2Frame: Buffer | undefined;
|
|
151
|
-
let negotiatedMtu = () => 23;
|
|
152
142
|
const handleWrite = (base64: string) => {
|
|
153
|
-
|
|
154
|
-
if (pro2BleReplies) {
|
|
155
|
-
if (frame[0] !== 0x5a && !pendingPro2Frame) return Promise.resolve();
|
|
156
|
-
pendingPro2Frame =
|
|
157
|
-
pendingPro2Frame && frame[0] !== 0x5a ? Buffer.concat([pendingPro2Frame, frame]) : frame;
|
|
158
|
-
const declaredLength =
|
|
159
|
-
pendingPro2Frame.length >= 3 ? pendingPro2Frame[1] + pendingPro2Frame[2] * 256 : Infinity;
|
|
160
|
-
if (pendingPro2Frame.length < declaredLength) return Promise.resolve();
|
|
161
|
-
frame = pendingPro2Frame;
|
|
162
|
-
pendingPro2Frame = undefined;
|
|
163
|
-
}
|
|
143
|
+
const frame = Buffer.from(base64, 'base64');
|
|
164
144
|
sentSeqs.push(frame[6]);
|
|
165
145
|
if (shouldRespond) {
|
|
166
146
|
responseSeq += 1;
|
|
167
147
|
const response = ProtocolV2.encodeFrame(
|
|
168
148
|
schemas,
|
|
169
149
|
'Success',
|
|
170
|
-
{ message:
|
|
150
|
+
{ message: 'ok' },
|
|
171
151
|
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
172
152
|
);
|
|
173
|
-
|
|
174
|
-
notifyCallback?.(null, { value: Buffer.from(notification).toString('base64') });
|
|
153
|
+
notifyCallback?.(null, { value: Buffer.from(response).toString('base64') });
|
|
175
154
|
}
|
|
176
155
|
return Promise.resolve();
|
|
177
156
|
};
|
|
@@ -191,11 +170,13 @@ const createHarness = ({
|
|
|
191
170
|
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
192
171
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
193
172
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
173
|
+
connect: jest.fn(),
|
|
194
174
|
onDisconnected: jest.fn(callback => {
|
|
195
175
|
disconnectCallback = callback;
|
|
196
176
|
return { remove: jest.fn() };
|
|
197
177
|
}),
|
|
198
178
|
} as any;
|
|
179
|
+
device.connect.mockResolvedValue(device);
|
|
199
180
|
device.requestMTU = jest.fn(() => Promise.resolve(device));
|
|
200
181
|
device.requestConnectionPriority = jest.fn(() => Promise.resolve(device));
|
|
201
182
|
const bleManager = {
|
|
@@ -215,14 +196,6 @@ const createHarness = ({
|
|
|
215
196
|
transport.init(logger, emitter);
|
|
216
197
|
transport.configure(protocolV1Schema);
|
|
217
198
|
transport.configureProtocolV2(protocolV2Schema);
|
|
218
|
-
// The Pro 2 sizes replies to the MTU of the link, i.e. the one the transport adopted.
|
|
219
|
-
negotiatedMtu = () => {
|
|
220
|
-
try {
|
|
221
|
-
return (transport as any).getCachedTransport(uuid).mtuSize ?? device.mtu;
|
|
222
|
-
} catch {
|
|
223
|
-
return device.mtu;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
199
|
|
|
227
200
|
return {
|
|
228
201
|
transport,
|
|
@@ -233,7 +206,6 @@ const createHarness = ({
|
|
|
233
206
|
bleManager,
|
|
234
207
|
sentSeqs,
|
|
235
208
|
writeCharacteristic,
|
|
236
|
-
notifyCharacteristic,
|
|
237
209
|
setShouldRespond(value: boolean) {
|
|
238
210
|
shouldRespond = value;
|
|
239
211
|
},
|
|
@@ -1157,42 +1129,106 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1157
1129
|
await transport.release(uuid, true);
|
|
1158
1130
|
});
|
|
1159
1131
|
|
|
1160
|
-
test('
|
|
1132
|
+
test('bounds a stalled MTU negotiation and continues protocol probing', async () => {
|
|
1161
1133
|
const { transport, uuid, device, bleManager } = createHarness();
|
|
1162
1134
|
device.mtu = 23;
|
|
1163
1135
|
device.requestMTU.mockImplementationOnce(() => new Promise(() => {}));
|
|
1164
|
-
const { resolveCharacteristics } = transport as any;
|
|
1165
1136
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
await expect(transport.acquire({ uuid })).rejects.toMatchObject({
|
|
1170
|
-
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1137
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1138
|
+
uuid,
|
|
1139
|
+
protocolType: 'V2',
|
|
1171
1140
|
});
|
|
1172
1141
|
const transactionId = device.requestMTU.mock.calls[0]?.[1];
|
|
1173
1142
|
expect(transactionId).toEqual(expect.stringContaining(`${uuid}:mtu:connected:0:`));
|
|
1174
1143
|
expect(bleManager.cancelTransaction).toHaveBeenCalledWith(transactionId);
|
|
1175
|
-
expect(
|
|
1176
|
-
expect(
|
|
1144
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
1145
|
+
expect(device.connect).toHaveBeenCalledWith(
|
|
1146
|
+
expect.objectContaining({ timeout: expect.any(Number) })
|
|
1147
|
+
);
|
|
1148
|
+
expect(device.connect.mock.calls.at(-1)?.[0]).not.toHaveProperty('requestMTU');
|
|
1149
|
+
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
1150
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
1151
|
+
await transport.release(uuid, true);
|
|
1177
1152
|
}, 10_000);
|
|
1178
1153
|
|
|
1179
|
-
|
|
1180
|
-
const
|
|
1154
|
+
test('does not request MTU again after connect falls back without requestMTU', async () => {
|
|
1155
|
+
const { BleError: BleErrorMock, BleErrorCode } = jest.requireMock('react-native-ble-plx');
|
|
1156
|
+
const { transport, uuid, device } = createHarness();
|
|
1157
|
+
device.mtu = 23;
|
|
1158
|
+
device.isConnected.mockResolvedValueOnce(false).mockResolvedValue(true);
|
|
1159
|
+
device.connect
|
|
1160
|
+
.mockRejectedValueOnce(
|
|
1161
|
+
Object.assign(new BleErrorMock('Operation was cancelled'), {
|
|
1162
|
+
errorCode: BleErrorCode.OperationCancelled,
|
|
1163
|
+
})
|
|
1164
|
+
)
|
|
1165
|
+
.mockResolvedValue(device);
|
|
1166
|
+
|
|
1167
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1168
|
+
uuid,
|
|
1169
|
+
protocolType: 'V2',
|
|
1170
|
+
});
|
|
1171
|
+
expect(device.connect).toHaveBeenCalledTimes(2);
|
|
1172
|
+
expect(device.connect.mock.calls[0][0]).toEqual(expect.objectContaining({ requestMTU: 247 }));
|
|
1173
|
+
expect(device.connect.mock.calls[1][0]).not.toHaveProperty('requestMTU');
|
|
1174
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1175
|
+
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
1176
|
+
await transport.release(uuid, true);
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
test('keeps the iOS connect chain unchanged', async () => {
|
|
1180
|
+
const { transport, uuid, device } = createHarness();
|
|
1181
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
1181
1182
|
|
|
1183
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1184
|
+
uuid,
|
|
1185
|
+
protocolType: 'V2',
|
|
1186
|
+
});
|
|
1187
|
+
expect(device.connect).toHaveBeenCalledWith({
|
|
1188
|
+
requestMTU: 247,
|
|
1189
|
+
timeout: expect.any(Number),
|
|
1190
|
+
refreshGatt: 'OnConnected',
|
|
1191
|
+
});
|
|
1192
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1193
|
+
await transport.release(uuid, true);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
describe('Android MTU before service discovery', () => {
|
|
1182
1197
|
beforeEach(() => {
|
|
1183
1198
|
setPlatformOS('android');
|
|
1184
|
-
|
|
1199
|
+
jest.useFakeTimers({
|
|
1200
|
+
doNotFake: ['setImmediate', 'queueMicrotask', 'nextTick', 'performance'],
|
|
1201
|
+
});
|
|
1185
1202
|
});
|
|
1186
1203
|
|
|
1187
1204
|
afterEach(() => {
|
|
1188
|
-
|
|
1205
|
+
jest.clearAllTimers();
|
|
1206
|
+
jest.useRealTimers();
|
|
1189
1207
|
});
|
|
1190
1208
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1209
|
+
const advanceUntil = async (condition: () => boolean, budgetMs = 60_000) => {
|
|
1210
|
+
for (let elapsed = 0; !condition(); elapsed += 50) {
|
|
1211
|
+
if (elapsed >= budgetMs) throw new Error(`fake time exhausted after ${budgetMs}ms`);
|
|
1212
|
+
jest.advanceTimersByTime(50);
|
|
1213
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1214
|
+
await new Promise(resolve => {
|
|
1215
|
+
setImmediate(resolve);
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
const settle = async <T>(promise: Promise<T>, budgetMs?: number): Promise<T> => {
|
|
1221
|
+
let done = false;
|
|
1222
|
+
promise.then(
|
|
1223
|
+
() => {
|
|
1224
|
+
done = true;
|
|
1225
|
+
},
|
|
1226
|
+
() => {
|
|
1227
|
+
done = true;
|
|
1228
|
+
}
|
|
1229
|
+
);
|
|
1230
|
+
await advanceUntil(() => done, budgetMs);
|
|
1231
|
+
return promise;
|
|
1196
1232
|
};
|
|
1197
1233
|
|
|
1198
1234
|
/**
|
|
@@ -1220,25 +1256,17 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1220
1256
|
return negotiated;
|
|
1221
1257
|
};
|
|
1222
1258
|
|
|
1223
|
-
const defaultMtuError = {
|
|
1224
|
-
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1225
|
-
message: expect.stringContaining('BLE link stayed at the default MTU'),
|
|
1226
|
-
};
|
|
1227
|
-
|
|
1228
1259
|
test('negotiates the MTU after a bare connect and before service discovery, adopting the returned device', async () => {
|
|
1229
1260
|
const { transport, uuid, device } = createHarness();
|
|
1230
1261
|
reconnectingDevice(device);
|
|
1231
1262
|
device.mtu = 23;
|
|
1232
1263
|
const negotiated = negotiatedSnapshot(device, 247);
|
|
1233
1264
|
|
|
1234
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1265
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1235
1266
|
uuid,
|
|
1236
1267
|
protocolType: 'V2',
|
|
1237
1268
|
});
|
|
1238
1269
|
|
|
1239
|
-
// No MTU request or GATT refresh inside the native connect budget: refreshGatt makes
|
|
1240
|
-
// the stack rediscover first, and a budget that expires mid-rediscovery closes the
|
|
1241
|
-
// client with the MTU request still unsent.
|
|
1242
1270
|
expect(device.connect).toHaveBeenCalledTimes(1);
|
|
1243
1271
|
expect(device.connect).toHaveBeenCalledWith({ timeout: expect.any(Number) });
|
|
1244
1272
|
const resolveCharacteristics = (transport as any).resolveCharacteristics as jest.Mock;
|
|
@@ -1250,25 +1278,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1250
1278
|
const cached = (transport as any).getCachedTransport(uuid);
|
|
1251
1279
|
expect(cached.device).toBe(negotiated);
|
|
1252
1280
|
expect(cached.mtuSize).toBe(247);
|
|
1253
|
-
await transport.release(uuid, true);
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
|
-
test('keeps the iOS connect chain unchanged', async () => {
|
|
1257
|
-
setPlatformOS('ios');
|
|
1258
|
-
const { transport, uuid, device } = createHarness();
|
|
1259
|
-
reconnectingDevice(device);
|
|
1260
|
-
|
|
1261
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1262
|
-
uuid,
|
|
1263
|
-
protocolType: 'V2',
|
|
1264
|
-
});
|
|
1265
|
-
expect(device.connect).toHaveBeenCalledWith({
|
|
1266
|
-
requestMTU: 247,
|
|
1267
|
-
timeout: expect.any(Number),
|
|
1268
|
-
refreshGatt: 'OnConnected',
|
|
1269
|
-
});
|
|
1270
|
-
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1271
|
-
await transport.release(uuid, true);
|
|
1281
|
+
await settle(transport.release(uuid, true));
|
|
1272
1282
|
});
|
|
1273
1283
|
|
|
1274
1284
|
test.each([
|
|
@@ -1276,164 +1286,53 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1276
1286
|
{ expectedProtocol: 'V1' as const, label: 'an expected Protocol V1 device' },
|
|
1277
1287
|
{ expectedProtocol: undefined, label: 'a device of unknown protocol' },
|
|
1278
1288
|
])('drops a link that stays at the default MTU for $label', async ({ expectedProtocol }) => {
|
|
1279
|
-
const { transport, uuid, device
|
|
1280
|
-
createHarness({ pro2BleReplies: true });
|
|
1281
|
-
fastAndroidWaits(transport);
|
|
1282
|
-
device.mtu = 23;
|
|
1283
|
-
// The exchange completes without raising the MTU.
|
|
1284
|
-
device.requestMTU.mockImplementation(() => Promise.resolve(device));
|
|
1285
|
-
|
|
1286
|
-
// Protocol V1 writes 192-byte packets whatever the MTU, and the fake Pro 2 would
|
|
1287
|
-
// answer the 29-byte probe with 20 bytes and stop, so the link is useless to either.
|
|
1288
|
-
await expect(transport.acquire({ uuid, expectedProtocol })).rejects.toMatchObject(
|
|
1289
|
-
defaultMtuError
|
|
1290
|
-
);
|
|
1291
|
-
// Refused before a transport exists, i.e. by acquire itself: no discovery, no
|
|
1292
|
-
// notification subscription, no write of any protocol.
|
|
1293
|
-
expect((transport as any).resolveCharacteristics).not.toHaveBeenCalled();
|
|
1294
|
-
expect(notifyCharacteristic.monitor).not.toHaveBeenCalled();
|
|
1295
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
1296
|
-
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
1297
|
-
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1298
|
-
expect(device.cancelConnection).toHaveBeenCalled();
|
|
1299
|
-
});
|
|
1300
|
-
|
|
1301
|
-
test('treats a fast native MTU rejection as a default-MTU link, not as a timeout', async () => {
|
|
1302
|
-
const { transport, uuid, device, bleManager } = createHarness();
|
|
1303
|
-
fastAndroidWaits(transport);
|
|
1304
|
-
device.mtu = 23;
|
|
1305
|
-
device.requestMTU.mockImplementation(() => Promise.reject(new Error('MTU request failed')));
|
|
1306
|
-
|
|
1307
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject(
|
|
1308
|
-
defaultMtuError
|
|
1309
|
-
);
|
|
1310
|
-
// No bounded wait ran, so nothing was abandoned mid-flight.
|
|
1311
|
-
expect(bleManager.cancelTransaction).not.toHaveBeenCalled();
|
|
1312
|
-
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1313
|
-
});
|
|
1314
|
-
|
|
1315
|
-
test('drops a link whose MTU exchange never completes and holds it past the quiet period', async () => {
|
|
1316
|
-
const { transport, uuid, device, bleManager } = createHarness();
|
|
1317
|
-
fastAndroidWaits(transport);
|
|
1289
|
+
const { transport, uuid, device } = createHarness();
|
|
1318
1290
|
device.mtu = 23;
|
|
1319
|
-
device.requestMTU.mockImplementation(() => new Promise(() => {}));
|
|
1320
|
-
// The GATT registry empties as soon as this client closes; that alone must not
|
|
1321
|
-
// release the wait, because the LE link outlives the client by the idle timer.
|
|
1322
|
-
connectedPeripherals().mockImplementation(() => Promise.resolve([]));
|
|
1323
1291
|
|
|
1324
|
-
|
|
1325
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
|
|
1292
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol }))).rejects.toMatchObject({
|
|
1326
1293
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1327
|
-
message: expect.stringContaining('BLE MTU exchange did not complete'),
|
|
1328
1294
|
});
|
|
1329
|
-
expect(Date.now() - startedAt).toBeGreaterThanOrEqual(
|
|
1330
|
-
transport.androidMtuExchangeTimeoutMs + transport.androidLinkDropQuietMs
|
|
1331
|
-
);
|
|
1332
|
-
expect(bleManager.cancelTransaction).toHaveBeenCalledWith(
|
|
1333
|
-
expect.stringContaining(`${uuid}:mtu:connected:0:`)
|
|
1334
|
-
);
|
|
1335
|
-
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1336
|
-
expect(device.cancelConnection).toHaveBeenCalled();
|
|
1337
1295
|
expect((transport as any).resolveCharacteristics).not.toHaveBeenCalled();
|
|
1296
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
1338
1297
|
});
|
|
1339
1298
|
|
|
1340
|
-
test(
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1299
|
+
test.each([
|
|
1300
|
+
['never completes', () => new Promise(() => {})],
|
|
1301
|
+
['completes at the default MTU', undefined],
|
|
1302
|
+
['is rejected', () => Promise.reject(new Error('MTU request failed'))],
|
|
1303
|
+
])(
|
|
1304
|
+
'a second consecutive MTU failure that %s trips the wedged-link guard',
|
|
1305
|
+
async (_label, requestMTU) => {
|
|
1306
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
1307
|
+
device.mtu = 23;
|
|
1308
|
+
if (requestMTU) device.requestMTU.mockImplementation(requestMTU);
|
|
1345
1309
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1310
|
+
await expect(
|
|
1311
|
+
settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))
|
|
1312
|
+
).rejects.toMatchObject({
|
|
1313
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1314
|
+
});
|
|
1315
|
+
await expect(
|
|
1316
|
+
settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))
|
|
1317
|
+
).rejects.toMatchObject({
|
|
1318
|
+
errorCode: HardwareErrorCode.PollingTimeout,
|
|
1319
|
+
message: expect.stringContaining(BLE_SETUP_WEDGED_MESSAGE),
|
|
1320
|
+
});
|
|
1321
|
+
expect(bleManager.destroy).toHaveBeenCalled();
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1355
1324
|
|
|
1356
1325
|
test('stop() during the link-drop wait releases it promptly', async () => {
|
|
1357
1326
|
const { transport, uuid, device } = createHarness();
|
|
1358
|
-
fastAndroidWaits(transport);
|
|
1359
|
-
transport.androidLinkDropQuietMs = 3000;
|
|
1360
|
-
transport.androidLinkDropTimeoutMs = 6000;
|
|
1361
1327
|
device.mtu = 23;
|
|
1362
1328
|
device.requestMTU.mockImplementation(() => new Promise(() => {}));
|
|
1363
1329
|
|
|
1364
1330
|
const acquiring = transport.acquire({ uuid, expectedProtocol: 'V2' }).catch(error => error);
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
await
|
|
1370
|
-
await expect(acquiring).resolves.toBeInstanceOf(Error);
|
|
1371
|
-
expect(Date.now() - stoppedAt).toBeLessThan(1500);
|
|
1372
|
-
}, 10_000);
|
|
1373
|
-
|
|
1374
|
-
test('does not reuse a cached transport that reports the default MTU', async () => {
|
|
1375
|
-
const { transport, uuid, device } = createHarness();
|
|
1376
|
-
fastAndroidWaits(transport);
|
|
1377
|
-
device.mtu = 23;
|
|
1378
|
-
negotiatedSnapshot(device, 247);
|
|
1379
|
-
|
|
1380
|
-
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1381
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(1);
|
|
1382
|
-
(transport as any).getCachedTransport(uuid).mtuSize = 23;
|
|
1383
|
-
|
|
1384
|
-
await expect(transport.call(uuid, 'Ping', { message: 'x' })).rejects.toMatchObject({
|
|
1385
|
-
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1386
|
-
message: expect.stringContaining('Protocol V2 needs a negotiated BLE MTU'),
|
|
1387
|
-
});
|
|
1388
|
-
// The next acquire goes through the full path and negotiates again instead of
|
|
1389
|
-
// handing the same unusable link back to every retry.
|
|
1390
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1391
|
-
uuid,
|
|
1392
|
-
protocolType: 'V2',
|
|
1393
|
-
});
|
|
1394
|
-
expect(device.requestMTU).toHaveBeenCalledTimes(2);
|
|
1395
|
-
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
|
|
1396
|
-
await transport.release(uuid, true);
|
|
1397
|
-
});
|
|
1398
|
-
|
|
1399
|
-
test('reassembles a Protocol V2 frame split across ATT writes at a negotiated MTU', async () => {
|
|
1400
|
-
const { transport, uuid, device, writeCharacteristic } = createHarness({
|
|
1401
|
-
pro2BleReplies: true,
|
|
1402
|
-
});
|
|
1403
|
-
device.mtu = 23;
|
|
1404
|
-
negotiatedSnapshot(device, 247);
|
|
1405
|
-
|
|
1406
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1407
|
-
uuid,
|
|
1408
|
-
protocolType: 'V2',
|
|
1409
|
-
});
|
|
1410
|
-
const writesBefore = writeCharacteristic.writeWithoutResponse.mock.calls.length;
|
|
1411
|
-
// Longer than one 244-byte packet, so the fake Pro 2 only answers once it has
|
|
1412
|
-
// reassembled both writes by the declared frame length.
|
|
1413
|
-
await expect(
|
|
1414
|
-
transport.call(uuid, 'Ping', { message: 'x'.repeat(300) })
|
|
1415
|
-
).resolves.toMatchObject({ type: 'Success' });
|
|
1416
|
-
expect(writeCharacteristic.writeWithoutResponse.mock.calls.length - writesBefore).toBe(2);
|
|
1417
|
-
await transport.release(uuid, true);
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
test('a reply larger than ATT_MTU-3 is cut by the fake Pro 2 and the call never completes', async () => {
|
|
1421
|
-
const { transport, uuid, device } = createHarness({ pro2BleReplies: true });
|
|
1422
|
-
fastAndroidWaits(transport);
|
|
1423
|
-
(transport as any).sessionProtocols.set(uuid, 'V2');
|
|
1424
|
-
reconnectingDevice(device);
|
|
1425
|
-
device.mtu = 23;
|
|
1426
|
-
// 30 passes the default-MTU check but only carries 27-byte notifications, short of
|
|
1427
|
-
// the 29-byte reply — the truncation seen in the capture at MTU 23.
|
|
1428
|
-
negotiatedSnapshot(device, 30);
|
|
1429
|
-
|
|
1430
|
-
await expect(
|
|
1431
|
-
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
1432
|
-
).resolves.toEqual({ uuid, protocolType: 'V2' });
|
|
1433
|
-
await expect(
|
|
1434
|
-
transport.call(uuid, 'Ping', { message: 'protocol-v2-probe' }, { timeoutMs: 300 })
|
|
1435
|
-
).rejects.toBeDefined();
|
|
1436
|
-
await transport.release(uuid, true);
|
|
1331
|
+
// The link-drop teardown cancels the device connection before the wait starts.
|
|
1332
|
+
await advanceUntil(() => device.cancelConnection.mock.calls.length > 0);
|
|
1333
|
+
await advanceUntil(() => false, 1000).catch(() => undefined);
|
|
1334
|
+
await settle(transport.stop(), 1000);
|
|
1335
|
+
await expect(settle(acquiring, 1000)).resolves.toBeInstanceOf(Error);
|
|
1437
1336
|
});
|
|
1438
1337
|
|
|
1439
1338
|
test.each([
|
|
@@ -1451,23 +1350,21 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1451
1350
|
ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable'),
|
|
1452
1351
|
},
|
|
1453
1352
|
])(
|
|
1454
|
-
'
|
|
1353
|
+
'marks the endpoint on $label and refreshes the GATT table on the next connect, before the MTU exchange',
|
|
1455
1354
|
async ({ error }) => {
|
|
1456
1355
|
const { transport, uuid, device } = createHarness();
|
|
1457
|
-
fastAndroidWaits(transport);
|
|
1458
1356
|
const link = reconnectingDevice(device);
|
|
1459
1357
|
device.mtu = 23;
|
|
1460
1358
|
negotiatedSnapshot(device, 247);
|
|
1461
1359
|
const resolveCharacteristics = (transport as any).resolveCharacteristics as jest.Mock;
|
|
1462
1360
|
resolveCharacteristics.mockImplementationOnce(() => Promise.reject(error()));
|
|
1463
1361
|
|
|
1464
|
-
await expect(
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
expect(
|
|
1468
|
-
expect(link.connected).toBe(false);
|
|
1362
|
+
await expect(
|
|
1363
|
+
settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))
|
|
1364
|
+
).rejects.toBeDefined();
|
|
1365
|
+
expect(link.connected).toBe(true);
|
|
1469
1366
|
|
|
1470
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1367
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1471
1368
|
uuid,
|
|
1472
1369
|
protocolType: 'V2',
|
|
1473
1370
|
});
|
|
@@ -1475,6 +1372,11 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1475
1372
|
timeout: expect.any(Number),
|
|
1476
1373
|
refreshGatt: 'OnConnected',
|
|
1477
1374
|
});
|
|
1375
|
+
// The stale table is only refreshed through a connect, so the live link is dropped first.
|
|
1376
|
+
const connectOrders: number[] = device.connect.mock.invocationCallOrder;
|
|
1377
|
+
expect(device.cancelConnection.mock.invocationCallOrder[0]).toBeLessThan(
|
|
1378
|
+
connectOrders[connectOrders.length - 1]
|
|
1379
|
+
);
|
|
1478
1380
|
const discoveryOrders = resolveCharacteristics.mock.invocationCallOrder;
|
|
1479
1381
|
const mtuOrders: number[] = device.requestMTU.mock.invocationCallOrder;
|
|
1480
1382
|
expect(discoveryOrders[discoveryOrders.length - 1]).toBeLessThan(
|
|
@@ -1482,17 +1384,16 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1482
1384
|
);
|
|
1483
1385
|
|
|
1484
1386
|
// The refresh is spent once the table resolved through a refreshed connect.
|
|
1485
|
-
await transport.release(uuid, true);
|
|
1387
|
+
await settle(transport.release(uuid, true));
|
|
1486
1388
|
link.connected = false;
|
|
1487
|
-
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1389
|
+
await settle(transport.acquire({ uuid, expectedProtocol: 'V2' }));
|
|
1488
1390
|
expect(device.connect).toHaveBeenLastCalledWith({ timeout: expect.any(Number) });
|
|
1489
|
-
await transport.release(uuid, true);
|
|
1391
|
+
await settle(transport.release(uuid, true));
|
|
1490
1392
|
}
|
|
1491
1393
|
);
|
|
1492
1394
|
|
|
1493
1395
|
test('keeps the refresh marker when the refresh connect fell back without refreshGatt', async () => {
|
|
1494
1396
|
const { transport, uuid, device } = createHarness();
|
|
1495
|
-
fastAndroidWaits(transport);
|
|
1496
1397
|
const link = reconnectingDevice(device);
|
|
1497
1398
|
device.mtu = 23;
|
|
1498
1399
|
negotiatedSnapshot(device, 247);
|
|
@@ -1500,115 +1401,142 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1500
1401
|
resolveCharacteristics.mockImplementationOnce(() =>
|
|
1501
1402
|
Promise.reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound))
|
|
1502
1403
|
);
|
|
1503
|
-
await expect(
|
|
1404
|
+
await expect(
|
|
1405
|
+
settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))
|
|
1406
|
+
).rejects.toBeDefined();
|
|
1504
1407
|
|
|
1505
|
-
// The
|
|
1506
|
-
// succeeds carries no refreshGatt, so the cache was never cleared.
|
|
1408
|
+
// The fallback connect carries no refreshGatt.
|
|
1507
1409
|
const cancelled = Object.assign(new Error('Operation was cancelled'), { errorCode: 2 });
|
|
1508
1410
|
device.connect.mockImplementationOnce(() => Promise.reject(cancelled));
|
|
1509
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1411
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1510
1412
|
uuid,
|
|
1511
1413
|
protocolType: 'V2',
|
|
1512
1414
|
});
|
|
1513
|
-
expect(device.connect).toHaveBeenLastCalledWith({ timeout: expect.any(Number) });
|
|
1514
1415
|
|
|
1515
|
-
await transport.release(uuid, true);
|
|
1416
|
+
await settle(transport.release(uuid, true));
|
|
1516
1417
|
link.connected = false;
|
|
1517
|
-
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1418
|
+
await settle(transport.acquire({ uuid, expectedProtocol: 'V2' }));
|
|
1518
1419
|
expect(device.connect).toHaveBeenLastCalledWith({
|
|
1519
1420
|
timeout: expect.any(Number),
|
|
1520
1421
|
refreshGatt: 'OnConnected',
|
|
1521
1422
|
});
|
|
1522
|
-
await transport.release(uuid, true);
|
|
1423
|
+
await settle(transport.release(uuid, true));
|
|
1523
1424
|
});
|
|
1524
1425
|
|
|
1525
|
-
test('
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
negotiatedSnapshot(device, 247);
|
|
1426
|
+
test('does not drop a link it has just connected with refreshGatt', async () => {
|
|
1427
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
1428
|
+
bleManager.devices.mockResolvedValue([]);
|
|
1429
|
+
const connectToDevice = jest.fn(() => Promise.resolve(device));
|
|
1430
|
+
Object.assign(bleManager, { connectToDevice });
|
|
1431
|
+
(transport as any).androidGattCacheRefreshes.add(uuid);
|
|
1532
1432
|
|
|
1533
|
-
await transport.acquire({ uuid, expectedProtocol: 'V2' })
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
);
|
|
1433
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1434
|
+
uuid,
|
|
1435
|
+
protocolType: 'V2',
|
|
1436
|
+
});
|
|
1437
|
+
expect(connectToDevice).toHaveBeenCalledTimes(1);
|
|
1438
|
+
expect(connectToDevice).toHaveBeenCalledWith(uuid, {
|
|
1439
|
+
timeout: expect.any(Number),
|
|
1440
|
+
refreshGatt: 'OnConnected',
|
|
1441
|
+
});
|
|
1442
|
+
expect(device.cancelConnection).not.toHaveBeenCalled();
|
|
1443
|
+
expect(device.connect).not.toHaveBeenCalled();
|
|
1444
|
+
await settle(transport.release(uuid, true));
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
test('still refreshes after a connect-by-id refresh fell back without refreshGatt', async () => {
|
|
1448
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
1449
|
+
const link = reconnectingDevice(device);
|
|
1450
|
+
bleManager.devices.mockResolvedValue([]);
|
|
1451
|
+
const cancelled = Object.assign(new Error('Operation was cancelled'), { errorCode: 2 });
|
|
1452
|
+
const connectToDevice = jest
|
|
1453
|
+
.fn()
|
|
1454
|
+
.mockImplementationOnce(() => Promise.reject(cancelled))
|
|
1455
|
+
.mockImplementation(() => {
|
|
1456
|
+
link.connected = true;
|
|
1457
|
+
return Promise.resolve(device);
|
|
1458
|
+
});
|
|
1459
|
+
Object.assign(bleManager, { connectToDevice });
|
|
1460
|
+
(transport as any).androidGattCacheRefreshes.add(uuid);
|
|
1542
1461
|
|
|
1543
|
-
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
|
|
1462
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1544
1463
|
uuid,
|
|
1545
1464
|
protocolType: 'V2',
|
|
1546
1465
|
});
|
|
1547
|
-
expect(
|
|
1466
|
+
expect(connectToDevice).toHaveBeenLastCalledWith(uuid, { timeout: expect.any(Number) });
|
|
1548
1467
|
expect(device.connect).toHaveBeenLastCalledWith({
|
|
1549
1468
|
timeout: expect.any(Number),
|
|
1550
1469
|
refreshGatt: 'OnConnected',
|
|
1551
1470
|
});
|
|
1552
|
-
|
|
1471
|
+
expect((transport as any).androidGattCacheRefreshes.has(uuid)).toBe(false);
|
|
1472
|
+
await settle(transport.release(uuid, true));
|
|
1553
1473
|
});
|
|
1554
1474
|
|
|
1555
|
-
test(
|
|
1556
|
-
|
|
1557
|
-
|
|
1475
|
+
test.each([
|
|
1476
|
+
'Cannot write client characteristic config descriptor',
|
|
1477
|
+
'Cannot find client characteristic config descriptor',
|
|
1478
|
+
'The handle is invalid',
|
|
1479
|
+
'Writing is not permitted',
|
|
1480
|
+
])('arms a GATT refresh from the notify failure "%s"', async reason => {
|
|
1481
|
+
const harness = createHarness();
|
|
1482
|
+
const { transport, uuid, device } = harness;
|
|
1558
1483
|
reconnectingDevice(device);
|
|
1484
|
+
device.mtu = 23;
|
|
1485
|
+
negotiatedSnapshot(device, 247);
|
|
1559
1486
|
|
|
1560
|
-
await
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
expect(
|
|
1487
|
+
await settle(transport.acquire({ uuid, expectedProtocol: 'V2' }));
|
|
1488
|
+
harness.emitMonitorError(Object.assign(new Error('notify failed'), { reason }));
|
|
1489
|
+
|
|
1490
|
+
await expect(settle(transport.acquire({ uuid, expectedProtocol: 'V2' }))).resolves.toEqual({
|
|
1491
|
+
uuid,
|
|
1492
|
+
protocolType: 'V2',
|
|
1493
|
+
});
|
|
1494
|
+
expect(device.connect).toHaveBeenLastCalledWith({
|
|
1564
1495
|
timeout: expect.any(Number),
|
|
1565
1496
|
refreshGatt: 'OnConnected',
|
|
1566
1497
|
});
|
|
1567
|
-
await transport.release(uuid, true);
|
|
1498
|
+
await settle(transport.release(uuid, true));
|
|
1568
1499
|
});
|
|
1569
1500
|
|
|
1570
|
-
test('a
|
|
1571
|
-
const { transport, uuid, device } = createHarness(
|
|
1572
|
-
|
|
1501
|
+
test('arms a GATT refresh from a notify failure while notifications are being enabled', async () => {
|
|
1502
|
+
const { transport, uuid, device } = createHarness({
|
|
1503
|
+
monitorError: Object.assign(new Error('notify failed'), {
|
|
1504
|
+
reason: 'Cannot write client characteristic config descriptor',
|
|
1505
|
+
}),
|
|
1506
|
+
});
|
|
1573
1507
|
const link = reconnectingDevice(device);
|
|
1574
1508
|
device.mtu = 23;
|
|
1575
|
-
|
|
1576
|
-
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
1577
|
-
const cached = (transport as any).getCachedTransport(uuid);
|
|
1578
|
-
const resolveCharacteristics = (transport as any).resolveCharacteristics as jest.Mock;
|
|
1579
|
-
device.connect.mockClear();
|
|
1580
|
-
device.requestMTU.mockClear();
|
|
1581
|
-
resolveCharacteristics.mockClear();
|
|
1509
|
+
negotiatedSnapshot(device, 247);
|
|
1582
1510
|
|
|
1511
|
+
await settle(transport.acquire({ uuid, expectedProtocol: 'V2' }).catch(error => error));
|
|
1512
|
+
await settle(transport.release(uuid, true));
|
|
1583
1513
|
link.connected = false;
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
expect(device.connect).toHaveBeenCalledWith({
|
|
1514
|
+
await settle(transport.acquire({ uuid, expectedProtocol: 'V2' }).catch(error => error));
|
|
1515
|
+
expect(device.connect).toHaveBeenLastCalledWith({
|
|
1588
1516
|
timeout: expect.any(Number),
|
|
1589
1517
|
refreshGatt: 'OnConnected',
|
|
1590
1518
|
});
|
|
1591
|
-
|
|
1592
|
-
const [discoveryOrder] = resolveCharacteristics.mock.invocationCallOrder;
|
|
1593
|
-
const [mtuOrder] = device.requestMTU.mock.invocationCallOrder;
|
|
1594
|
-
expect(connectOrder).toBeLessThan(discoveryOrder);
|
|
1595
|
-
expect(discoveryOrder).toBeLessThan(mtuOrder);
|
|
1596
|
-
expect(cached.device).toBe(negotiated);
|
|
1597
|
-
expect(cached.mtuSize).toBe(247);
|
|
1598
|
-
await transport.release(uuid, true);
|
|
1519
|
+
await settle(transport.release(uuid, true));
|
|
1599
1520
|
});
|
|
1600
|
-
});
|
|
1601
1521
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1522
|
+
test('keeps the GATT refresh for a firmware-install reconnect', async () => {
|
|
1523
|
+
const { transport, uuid, device } = createHarness();
|
|
1524
|
+
(transport as any).sessionProtocols.set(uuid, 'V2');
|
|
1525
|
+
reconnectingDevice(device);
|
|
1606
1526
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1527
|
+
await expect(
|
|
1528
|
+
settle(transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true }))
|
|
1529
|
+
).resolves.toEqual({ uuid, protocolType: 'V2' });
|
|
1530
|
+
expect(device.connect).toHaveBeenCalledWith({
|
|
1531
|
+
timeout: expect.any(Number),
|
|
1532
|
+
refreshGatt: 'OnConnected',
|
|
1533
|
+
});
|
|
1534
|
+
await settle(transport.release(uuid, true));
|
|
1535
|
+
});
|
|
1609
1536
|
});
|
|
1610
1537
|
|
|
1611
1538
|
test('spends one Initialize wake on the detection after a fully silent one', async () => {
|
|
1539
|
+
setPlatformOS('android');
|
|
1612
1540
|
const { transport, uuid } = createHarness();
|
|
1613
1541
|
const probes = transport as any;
|
|
1614
1542
|
jest.spyOn(probes, 'probeProtocolV1').mockResolvedValue(false);
|
|
@@ -1632,6 +1560,35 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1632
1560
|
expect(callProtocolV1).not.toHaveBeenCalled();
|
|
1633
1561
|
});
|
|
1634
1562
|
|
|
1563
|
+
test('does not send the Initialize wake on iOS', async () => {
|
|
1564
|
+
const { transport, uuid } = createHarness();
|
|
1565
|
+
const probes = transport as any;
|
|
1566
|
+
jest.spyOn(probes, 'probeProtocolV1').mockResolvedValue(false);
|
|
1567
|
+
jest.spyOn(probes, 'probeProtocolV2').mockResolvedValue(false);
|
|
1568
|
+
const callProtocolV1 = jest.spyOn(probes, 'callProtocolV1').mockResolvedValue({});
|
|
1569
|
+
|
|
1570
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1571
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1572
|
+
expect(callProtocolV1).not.toHaveBeenCalled();
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
test('an unanswered Initialize wake settles the acquire and frees the lifecycle lock', async () => {
|
|
1576
|
+
setPlatformOS('android');
|
|
1577
|
+
const harness = createHarness();
|
|
1578
|
+
const { transport, uuid } = harness;
|
|
1579
|
+
const probes = transport as any;
|
|
1580
|
+
harness.setShouldRespond(false);
|
|
1581
|
+
jest.spyOn(probes, 'probeProtocolV1').mockResolvedValue(false);
|
|
1582
|
+
jest.spyOn(probes, 'probeProtocolV2').mockResolvedValue(false);
|
|
1583
|
+
|
|
1584
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1585
|
+
await expect(transport.acquire({ uuid })).rejects.toMatchObject({
|
|
1586
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
1587
|
+
});
|
|
1588
|
+
await transport.disconnect(uuid);
|
|
1589
|
+
await transport.stop();
|
|
1590
|
+
}, 10_000);
|
|
1591
|
+
|
|
1635
1592
|
test('accepts a stable low MTU without the delayed refresh loop', async () => {
|
|
1636
1593
|
const { transport, uuid, device } = createHarness();
|
|
1637
1594
|
device.mtu = 185;
|