@onekeyfe/hd-transport-react-native 1.2.0-alpha.6 → 1.2.0-alpha.60
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/BleTransport.d.ts +1 -3
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +0 -2
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +21 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +401 -367
- package/dist/subscribeBleOn.d.ts.map +1 -1
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +7 -6
- package/src/BleTransport.ts +6 -41
- package/src/__tests__/BleTransport.test.ts +93 -0
- package/src/__tests__/bleStrategy.test.ts +1 -6
- package/src/__tests__/constants.test.ts +20 -0
- package/src/__tests__/enumerate.test.ts +132 -0
- package/src/__tests__/protocolV2Link.test.ts +747 -0
- package/src/bleStrategy.ts +0 -25
- package/src/constants.ts +7 -13
- package/src/index.ts +472 -386
- package/src/subscribeBleOn.ts +0 -2
- package/src/transportLog.ts +1 -0
- package/src/types.ts +1 -0
package/src/index.ts
CHANGED
|
@@ -12,26 +12,31 @@ import transport, {
|
|
|
12
12
|
LogBlockCommand,
|
|
13
13
|
type OneKeyDeviceInfoBase,
|
|
14
14
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
15
|
+
PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
15
16
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
16
17
|
type ProtocolType,
|
|
18
|
+
type ProtocolV2CallContext,
|
|
17
19
|
ProtocolV2FrameAssembler,
|
|
18
|
-
|
|
20
|
+
ProtocolV2LinkManager,
|
|
21
|
+
TRANSPORT_EVENT,
|
|
19
22
|
type TransportCallOptions,
|
|
20
23
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
24
|
+
writeProtocolV2BleFrame,
|
|
21
25
|
} from '@onekeyfe/hd-transport';
|
|
22
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ERRORS,
|
|
28
|
+
HardwareErrorCode,
|
|
29
|
+
createDeferred,
|
|
30
|
+
isOnekeyBluetoothDevice,
|
|
31
|
+
isPro2FindMyAdvertisementName,
|
|
32
|
+
} from '@onekeyfe/hd-shared';
|
|
23
33
|
|
|
24
34
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
35
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
30
36
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
37
|
import {
|
|
32
38
|
ANDROID_PACKET_LENGTH,
|
|
33
39
|
IOS_PACKET_LENGTH,
|
|
34
|
-
getBleUuidKey,
|
|
35
40
|
getBluetoothServiceUuids,
|
|
36
41
|
getInfosForServiceUuid,
|
|
37
42
|
isSameBleUuid,
|
|
@@ -40,6 +45,7 @@ import { isHeaderChunk } from './utils/validateNotify';
|
|
|
40
45
|
import BleTransport from './BleTransport';
|
|
41
46
|
import timer from './utils/timer';
|
|
42
47
|
import { bleLogger, setBleLogger } from './logger';
|
|
48
|
+
import { createTransportCallLog } from './transportLog';
|
|
43
49
|
|
|
44
50
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
51
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
@@ -55,13 +61,13 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
55
61
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
56
62
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
57
63
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
58
|
-
const
|
|
64
|
+
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
59
65
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
60
66
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
61
67
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
62
68
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
63
69
|
|
|
64
|
-
type FirmwareUploadWriteRetryType = 'congested'
|
|
70
|
+
type FirmwareUploadWriteRetryType = 'congested';
|
|
65
71
|
type ResolvedBleCharacteristics = {
|
|
66
72
|
writeCharacteristic: Characteristic;
|
|
67
73
|
notifyCharacteristic: Characteristic;
|
|
@@ -72,7 +78,9 @@ const delay = (ms: number) =>
|
|
|
72
78
|
setTimeout(resolve, ms);
|
|
73
79
|
});
|
|
74
80
|
|
|
75
|
-
const getFirmwareUploadWriteRetryType = (
|
|
81
|
+
export const getFirmwareUploadWriteRetryType = (
|
|
82
|
+
error: unknown
|
|
83
|
+
): FirmwareUploadWriteRetryType | null => {
|
|
76
84
|
if (!error || typeof error !== 'object') return null;
|
|
77
85
|
const bleWriteError = error as {
|
|
78
86
|
androidErrorCode?: unknown;
|
|
@@ -83,13 +91,6 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
83
91
|
name?: unknown;
|
|
84
92
|
};
|
|
85
93
|
|
|
86
|
-
if (
|
|
87
|
-
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
88
|
-
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
89
|
-
) {
|
|
90
|
-
return 'reconnectable';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
94
|
if (
|
|
94
95
|
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
95
96
|
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
@@ -105,23 +106,14 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
105
106
|
|
|
106
107
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
107
108
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
108
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
109
109
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
110
110
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
111
|
-
const DEVICE_SCAN_TIMEOUT_MS =
|
|
111
|
+
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
112
112
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
113
113
|
const ANDROID_NOTIFY_READY_DELAY_MS = 300;
|
|
114
|
-
const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
|
|
115
|
-
const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
|
|
116
|
-
const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
|
|
117
|
-
|
|
118
114
|
export type ProtocolV2BleTuning = {
|
|
119
115
|
iosPacketLength?: number;
|
|
120
116
|
androidPacketLength?: number;
|
|
121
|
-
highVolumeWriteBurstSize?: number;
|
|
122
|
-
highVolumeWritePauseMs?: number;
|
|
123
|
-
highVolumeWriteFlushDelayMs?: number;
|
|
124
|
-
highVolumeWriteWithResponse?: boolean;
|
|
125
117
|
};
|
|
126
118
|
|
|
127
119
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
@@ -129,10 +121,6 @@ type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
|
129
121
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
130
122
|
iosPacketLength: IOS_PACKET_LENGTH,
|
|
131
123
|
androidPacketLength: ANDROID_PACKET_LENGTH,
|
|
132
|
-
highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
|
|
133
|
-
highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
|
|
134
|
-
highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
|
|
135
|
-
highVolumeWriteWithResponse: false,
|
|
136
124
|
};
|
|
137
125
|
|
|
138
126
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -153,27 +141,13 @@ export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
|
153
141
|
tuning.androidPacketLength,
|
|
154
142
|
protocolV2BleTuning.androidPacketLength
|
|
155
143
|
),
|
|
156
|
-
highVolumeWriteBurstSize: normalizePositiveInteger(
|
|
157
|
-
tuning.highVolumeWriteBurstSize,
|
|
158
|
-
protocolV2BleTuning.highVolumeWriteBurstSize
|
|
159
|
-
),
|
|
160
|
-
highVolumeWritePauseMs: normalizePositiveInteger(
|
|
161
|
-
tuning.highVolumeWritePauseMs,
|
|
162
|
-
protocolV2BleTuning.highVolumeWritePauseMs
|
|
163
|
-
),
|
|
164
|
-
highVolumeWriteFlushDelayMs: normalizePositiveInteger(
|
|
165
|
-
tuning.highVolumeWriteFlushDelayMs,
|
|
166
|
-
protocolV2BleTuning.highVolumeWriteFlushDelayMs
|
|
167
|
-
),
|
|
168
|
-
highVolumeWriteWithResponse:
|
|
169
|
-
tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
|
|
170
144
|
};
|
|
171
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
145
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
|
|
172
146
|
}
|
|
173
147
|
|
|
174
148
|
export function resetProtocolV2BleTuning() {
|
|
175
149
|
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
176
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
150
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
|
|
177
151
|
}
|
|
178
152
|
|
|
179
153
|
export function getProtocolV2BleTuning() {
|
|
@@ -188,16 +162,6 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
188
162
|
return device?.name || device?.localName || null;
|
|
189
163
|
}
|
|
190
164
|
|
|
191
|
-
function isGenericBleService(uuid?: string | null) {
|
|
192
|
-
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function hasKnownOneKeyService(device?: Device | null) {
|
|
196
|
-
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
197
|
-
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
165
|
const ANDROID_REQUEST_MTU = 256;
|
|
202
166
|
|
|
203
167
|
const connectOptions: Record<string, unknown> = {
|
|
@@ -222,9 +186,10 @@ const requestAndroidMtu = async (device: Device) => {
|
|
|
222
186
|
|
|
223
187
|
try {
|
|
224
188
|
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
189
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
190
|
+
deviceId: device.id,
|
|
226
191
|
requested: ANDROID_REQUEST_MTU,
|
|
227
|
-
|
|
192
|
+
actual: mtuDevice.mtu,
|
|
228
193
|
});
|
|
229
194
|
return mtuDevice;
|
|
230
195
|
} catch (error) {
|
|
@@ -272,6 +237,8 @@ export default class ReactNativeBleTransport {
|
|
|
272
237
|
|
|
273
238
|
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
274
239
|
|
|
240
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
241
|
+
|
|
275
242
|
name = 'ReactNativeBleTransport';
|
|
276
243
|
|
|
277
244
|
configured = false;
|
|
@@ -282,6 +249,8 @@ export default class ReactNativeBleTransport {
|
|
|
282
249
|
|
|
283
250
|
runPromise: Deferred<any> | null = null;
|
|
284
251
|
|
|
252
|
+
private runPromiseDeviceId: string | null = null;
|
|
253
|
+
|
|
285
254
|
emitter?: EventEmitter;
|
|
286
255
|
|
|
287
256
|
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
@@ -297,12 +266,31 @@ export default class ReactNativeBleTransport {
|
|
|
297
266
|
|
|
298
267
|
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
268
|
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
269
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
270
|
+
getSchemas: () => {
|
|
271
|
+
if (!this._messages || !this._messagesV2) {
|
|
272
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
protocolV1: this._messages,
|
|
276
|
+
protocolV2: this._messagesV2,
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
classifyError: () => 'link-fatal',
|
|
280
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
281
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
282
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
283
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
284
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
285
|
+
await this.releaseNative(uuid, true);
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
});
|
|
303
289
|
|
|
304
290
|
private monitorTokens: Map<string, number> = new Map();
|
|
305
291
|
|
|
292
|
+
private disconnectEventTokens: Map<string, number> = new Map();
|
|
293
|
+
|
|
306
294
|
private nextMonitorToken = 1;
|
|
307
295
|
|
|
308
296
|
constructor(options: TransportOptions) {
|
|
@@ -321,8 +309,19 @@ export default class ReactNativeBleTransport {
|
|
|
321
309
|
}
|
|
322
310
|
|
|
323
311
|
configureProtocolV2(signedData: any) {
|
|
312
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
313
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
324
318
|
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
-
|
|
319
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
320
|
+
if (isReconfiguration) {
|
|
321
|
+
this.protocolV2Links
|
|
322
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
323
|
+
.catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
324
|
+
}
|
|
326
325
|
}
|
|
327
326
|
|
|
328
327
|
listen() {
|
|
@@ -352,29 +351,15 @@ export default class ReactNativeBleTransport {
|
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
353
|
|
|
355
|
-
let fallbackServiceUuid: string | undefined;
|
|
356
|
-
|
|
357
354
|
if (!infos) {
|
|
358
355
|
const services = await device.services();
|
|
359
356
|
Log?.debug(
|
|
360
357
|
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
361
358
|
services?.map(service => service.uuid)
|
|
362
359
|
);
|
|
363
|
-
|
|
364
|
-
const knownService = services.find(service =>
|
|
365
|
-
getInfosForServiceUuid(service.uuid, 'classic')
|
|
366
|
-
);
|
|
367
|
-
const fallbackService =
|
|
368
|
-
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
369
|
-
|
|
370
|
-
if (fallbackService) {
|
|
371
|
-
fallbackServiceUuid = fallbackService.uuid;
|
|
372
|
-
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
373
|
-
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
374
|
-
}
|
|
375
360
|
}
|
|
376
361
|
|
|
377
|
-
if (!infos
|
|
362
|
+
if (!infos) {
|
|
378
363
|
try {
|
|
379
364
|
Log?.debug('cancel connection when service not found');
|
|
380
365
|
await device.cancelConnection();
|
|
@@ -384,9 +369,7 @@ export default class ReactNativeBleTransport {
|
|
|
384
369
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
385
370
|
}
|
|
386
371
|
|
|
387
|
-
const serviceUuid = infos
|
|
388
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
389
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
372
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
390
373
|
|
|
391
374
|
if (!serviceUuid) {
|
|
392
375
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
@@ -436,6 +419,7 @@ export default class ReactNativeBleTransport {
|
|
|
436
419
|
|
|
437
420
|
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
|
|
438
421
|
transport.disconnectSubscription?.remove();
|
|
422
|
+
const { monitorToken } = transport;
|
|
439
423
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
440
424
|
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
441
425
|
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
@@ -445,18 +429,17 @@ export default class ReactNativeBleTransport {
|
|
|
445
429
|
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
446
430
|
return;
|
|
447
431
|
}
|
|
432
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) {
|
|
433
|
+
Log?.debug('device disconnect ignored for stale generation: ', device?.id);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
448
436
|
|
|
449
437
|
try {
|
|
450
438
|
Log?.debug('device disconnect: ', device?.id);
|
|
451
|
-
this.
|
|
452
|
-
|
|
453
|
-
id: device?.id,
|
|
454
|
-
connectId: device?.id,
|
|
455
|
-
});
|
|
456
|
-
if (this.runPromise) {
|
|
439
|
+
this.emitDeviceDisconnect(uuid, device?.name, monitorToken);
|
|
440
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
457
441
|
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
458
442
|
this.runPromise.reject(error);
|
|
459
|
-
this.rejectAllProtocolV2Frames(error);
|
|
460
443
|
}
|
|
461
444
|
} catch (e) {
|
|
462
445
|
Log?.debug('device disconnect error: ', e);
|
|
@@ -466,6 +449,22 @@ export default class ReactNativeBleTransport {
|
|
|
466
449
|
});
|
|
467
450
|
}
|
|
468
451
|
|
|
452
|
+
private emitDeviceDisconnect(uuid: string, name: string | null | undefined, token?: number) {
|
|
453
|
+
if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (this.monitorTokens.get(uuid) !== token) {
|
|
457
|
+
Log?.debug('device disconnect event ignored for stale generation: ', uuid);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
this.disconnectEventTokens.set(uuid, token);
|
|
461
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
462
|
+
name,
|
|
463
|
+
id: uuid,
|
|
464
|
+
connectId: uuid,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
469
468
|
async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
|
|
470
469
|
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
471
470
|
try {
|
|
@@ -553,14 +552,13 @@ export default class ReactNativeBleTransport {
|
|
|
553
552
|
}
|
|
554
553
|
|
|
555
554
|
blePlxManager.startDeviceScan(
|
|
556
|
-
|
|
555
|
+
getBluetoothServiceUuids(),
|
|
557
556
|
{
|
|
558
557
|
allowDuplicates: true,
|
|
559
558
|
scanMode: ScanMode.LowLatency,
|
|
560
559
|
},
|
|
561
560
|
(error, device) => {
|
|
562
561
|
if (error) {
|
|
563
|
-
Log?.debug('ble scan manager: ', blePlxManager);
|
|
564
562
|
Log?.debug('ble scan error: ', error);
|
|
565
563
|
if (
|
|
566
564
|
[BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
|
|
@@ -584,33 +582,23 @@ export default class ReactNativeBleTransport {
|
|
|
584
582
|
}
|
|
585
583
|
|
|
586
584
|
const displayName = getDeviceDisplayName(device);
|
|
585
|
+
// iOS may report a service-only advertisement before the named scan response.
|
|
586
|
+
// Do not cache that incomplete advertisement as an unknown device.
|
|
587
|
+
const isUnnamedIOSPeripheral = Platform.OS === 'ios' && !displayName?.trim();
|
|
588
|
+
const isFindMyPeripheral =
|
|
589
|
+
isPro2FindMyAdvertisementName(device?.name) ||
|
|
590
|
+
isPro2FindMyAdvertisementName(device?.localName);
|
|
587
591
|
const isOneKey =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
!!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
|
|
593
|
-
|
|
594
|
-
if (shouldTraceCandidate) {
|
|
595
|
-
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
592
|
+
!isUnnamedIOSPeripheral &&
|
|
593
|
+
!isFindMyPeripheral &&
|
|
594
|
+
isOnekeyBluetoothDevice({
|
|
595
|
+
id: device?.id,
|
|
596
596
|
name: device?.name,
|
|
597
597
|
localName: device?.localName,
|
|
598
|
-
|
|
599
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
600
|
-
accepted: isOneKey,
|
|
598
|
+
serviceUuids: device?.serviceUUIDs,
|
|
601
599
|
});
|
|
602
|
-
}
|
|
603
|
-
|
|
604
600
|
if (isOneKey) {
|
|
605
|
-
Log?.debug('search device start ======================');
|
|
606
|
-
const { name, localName, id, serviceUUIDs } = device ?? {};
|
|
607
|
-
Log?.debug(
|
|
608
|
-
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
|
|
609
|
-
id ?? ''
|
|
610
|
-
}\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
|
|
611
|
-
);
|
|
612
601
|
addDevice(device as unknown as Device);
|
|
613
|
-
Log?.debug('search device end ======================\n');
|
|
614
602
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
603
|
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
604
|
name: device?.name,
|
|
@@ -622,12 +610,32 @@ export default class ReactNativeBleTransport {
|
|
|
622
610
|
}
|
|
623
611
|
);
|
|
624
612
|
|
|
625
|
-
getConnectedDeviceIds(getBluetoothServiceUuids()).then(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
613
|
+
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
614
|
+
devices => {
|
|
615
|
+
for (const device of devices) {
|
|
616
|
+
const localName =
|
|
617
|
+
'localName' in device && typeof device.localName === 'string'
|
|
618
|
+
? device.localName
|
|
619
|
+
: null;
|
|
620
|
+
const isFindMyPeripheral =
|
|
621
|
+
isPro2FindMyAdvertisementName(device.name) ||
|
|
622
|
+
isPro2FindMyAdvertisementName(localName);
|
|
623
|
+
|
|
624
|
+
if (
|
|
625
|
+
!isFindMyPeripheral &&
|
|
626
|
+
isOnekeyBluetoothDevice({
|
|
627
|
+
id: device.id,
|
|
628
|
+
name: device.name,
|
|
629
|
+
localName,
|
|
630
|
+
serviceUuids: device.serviceUUIDs,
|
|
631
|
+
})
|
|
632
|
+
) {
|
|
633
|
+
Log?.debug('search connected peripheral: ', device.id);
|
|
634
|
+
addDevice(device as unknown as Device);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
629
637
|
}
|
|
630
|
-
|
|
638
|
+
);
|
|
631
639
|
|
|
632
640
|
const addDevice = (device: Device) => {
|
|
633
641
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
@@ -641,6 +649,12 @@ export default class ReactNativeBleTransport {
|
|
|
641
649
|
name: displayName,
|
|
642
650
|
commType: 'ble',
|
|
643
651
|
} as IOneKeyDevice);
|
|
652
|
+
Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
|
|
653
|
+
deviceId: device.id,
|
|
654
|
+
name: displayName,
|
|
655
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
656
|
+
protocolHint,
|
|
657
|
+
});
|
|
644
658
|
}
|
|
645
659
|
};
|
|
646
660
|
|
|
@@ -651,6 +665,46 @@ export default class ReactNativeBleTransport {
|
|
|
651
665
|
});
|
|
652
666
|
}
|
|
653
667
|
|
|
668
|
+
private async installTransportForAcquire(
|
|
669
|
+
uuid: string,
|
|
670
|
+
device: Device,
|
|
671
|
+
characteristics?: ResolvedBleCharacteristics
|
|
672
|
+
) {
|
|
673
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
674
|
+
characteristics ?? (await this.resolveCharacteristics(device));
|
|
675
|
+
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
676
|
+
if (Platform.OS === 'android') {
|
|
677
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
678
|
+
}
|
|
679
|
+
const monitorToken = this.nextMonitorToken;
|
|
680
|
+
this.nextMonitorToken += 1;
|
|
681
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
682
|
+
transport.monitorToken = monitorToken;
|
|
683
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
684
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
685
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
686
|
+
transport.notifyCharacteristic,
|
|
687
|
+
uuid,
|
|
688
|
+
monitorToken,
|
|
689
|
+
notifyTransactionId
|
|
690
|
+
);
|
|
691
|
+
transportCache[uuid] = transport;
|
|
692
|
+
this.protocolV2Assemblers.set(
|
|
693
|
+
uuid,
|
|
694
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
if (Platform.OS === 'ios') {
|
|
698
|
+
await new Promise<void>(resolve => {
|
|
699
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
700
|
+
});
|
|
701
|
+
} else if (Platform.OS === 'android') {
|
|
702
|
+
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return transport;
|
|
706
|
+
}
|
|
707
|
+
|
|
654
708
|
async acquire(input: BleAcquireInput) {
|
|
655
709
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
656
710
|
|
|
@@ -684,9 +738,8 @@ export default class ReactNativeBleTransport {
|
|
|
684
738
|
if (forceCleanRunPromise && this.runPromise) {
|
|
685
739
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
686
740
|
this.runPromise.reject(error);
|
|
687
|
-
this.rejectAllProtocolV2Frames(error);
|
|
688
741
|
this.runPromise = null;
|
|
689
|
-
this.
|
|
742
|
+
this.runPromiseDeviceId = null;
|
|
690
743
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
691
744
|
}
|
|
692
745
|
|
|
@@ -775,12 +828,16 @@ export default class ReactNativeBleTransport {
|
|
|
775
828
|
}
|
|
776
829
|
|
|
777
830
|
device = await requestAndroidMtu(device);
|
|
778
|
-
const
|
|
831
|
+
const acquiredDevice = device;
|
|
832
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
833
|
+
acquiredDevice
|
|
834
|
+
);
|
|
779
835
|
|
|
780
836
|
const protocolHint = expectedProtocol
|
|
781
837
|
? undefined
|
|
782
|
-
:
|
|
783
|
-
|
|
838
|
+
: input.protocolHint ??
|
|
839
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
840
|
+
inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
784
841
|
|
|
785
842
|
// release transport before new transport instance
|
|
786
843
|
await this.release(uuid, true);
|
|
@@ -788,45 +845,30 @@ export default class ReactNativeBleTransport {
|
|
|
788
845
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
789
846
|
}
|
|
790
847
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
const monitorToken = this.nextMonitorToken;
|
|
796
|
-
this.nextMonitorToken += 1;
|
|
797
|
-
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
798
|
-
transport.monitorToken = monitorToken;
|
|
799
|
-
transport.notifyTransactionId = notifyTransactionId;
|
|
800
|
-
this.monitorTokens.set(uuid, monitorToken);
|
|
801
|
-
transport.notifySubscription = this._monitorCharacteristic(
|
|
802
|
-
transport.notifyCharacteristic,
|
|
803
|
-
uuid,
|
|
804
|
-
monitorToken,
|
|
805
|
-
notifyTransactionId
|
|
806
|
-
);
|
|
807
|
-
transportCache[uuid] = transport;
|
|
808
|
-
|
|
809
|
-
this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
|
|
810
|
-
|
|
811
|
-
if (Platform.OS === 'ios') {
|
|
812
|
-
await new Promise<void>(resolve => {
|
|
813
|
-
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
814
|
-
});
|
|
815
|
-
} else if (Platform.OS === 'android') {
|
|
816
|
-
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
|
|
820
|
-
|
|
821
|
-
this.emitter?.emit('device-connect', {
|
|
822
|
-
name: device.name,
|
|
823
|
-
id: device.id,
|
|
824
|
-
connectId: device.id,
|
|
848
|
+
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
849
|
+
writeCharacteristic,
|
|
850
|
+
notifyCharacteristic,
|
|
825
851
|
});
|
|
826
852
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
853
|
+
try {
|
|
854
|
+
const protocolType = await this.detectProtocol(
|
|
855
|
+
uuid,
|
|
856
|
+
expectedProtocol,
|
|
857
|
+
protocolHint,
|
|
858
|
+
async () => {
|
|
859
|
+
await this.installTransportForAcquire(uuid, acquiredDevice);
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
const currentTransport = transportCache[uuid];
|
|
863
|
+
if (!currentTransport) {
|
|
864
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
865
|
+
}
|
|
866
|
+
this.attachDisconnectSubscription(currentTransport, acquiredDevice, uuid);
|
|
867
|
+
return { uuid, protocolType };
|
|
868
|
+
} catch (error) {
|
|
869
|
+
await this.release(uuid, true);
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
830
872
|
}
|
|
831
873
|
|
|
832
874
|
_monitorCharacteristic(
|
|
@@ -853,7 +895,30 @@ export default class ReactNativeBleTransport {
|
|
|
853
895
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
896
|
return;
|
|
855
897
|
}
|
|
856
|
-
if (this.
|
|
898
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
899
|
+
let errorCode:
|
|
900
|
+
| typeof HardwareErrorCode.BleDeviceBondError
|
|
901
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
902
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
903
|
+
| typeof HardwareErrorCode.BleTimeoutError =
|
|
904
|
+
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
905
|
+
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
906
|
+
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
907
|
+
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
908
|
+
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
909
|
+
} else if (
|
|
910
|
+
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
911
|
+
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
912
|
+
error.reason?.includes('The handle is invalid') ||
|
|
913
|
+
error.reason?.includes('Writing is not permitted') ||
|
|
914
|
+
error.reason?.includes('notify change failed for device')
|
|
915
|
+
) {
|
|
916
|
+
errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
917
|
+
}
|
|
918
|
+
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
857
922
|
let ERROR:
|
|
858
923
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
859
924
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -876,7 +941,6 @@ export default class ReactNativeBleTransport {
|
|
|
876
941
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
877
942
|
);
|
|
878
943
|
this.runPromise.reject(notifyError);
|
|
879
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
880
944
|
Log?.debug(
|
|
881
945
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
882
946
|
);
|
|
@@ -884,7 +948,6 @@ export default class ReactNativeBleTransport {
|
|
|
884
948
|
}
|
|
885
949
|
const notifyError = ERRORS.TypedError(ERROR);
|
|
886
950
|
this.runPromise.reject(notifyError);
|
|
887
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
888
951
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
889
952
|
}
|
|
890
953
|
|
|
@@ -908,7 +971,7 @@ export default class ReactNativeBleTransport {
|
|
|
908
971
|
return;
|
|
909
972
|
}
|
|
910
973
|
if (protocol === 'V2') {
|
|
911
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
974
|
+
this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
|
|
912
975
|
return;
|
|
913
976
|
}
|
|
914
977
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
@@ -929,13 +992,18 @@ export default class ReactNativeBleTransport {
|
|
|
929
992
|
// );
|
|
930
993
|
bufferLength = 0;
|
|
931
994
|
buffer = [];
|
|
932
|
-
this.
|
|
995
|
+
if (this.runPromiseDeviceId === uuid) {
|
|
996
|
+
this.runPromise?.resolve(value.toString('hex'));
|
|
997
|
+
}
|
|
933
998
|
}
|
|
934
999
|
} catch (error) {
|
|
935
1000
|
Log?.debug('monitor data error: ', error);
|
|
936
1001
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
-
this.
|
|
938
|
-
|
|
1002
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
1003
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1004
|
+
} else if (this.runPromiseDeviceId === uuid) {
|
|
1005
|
+
this.runPromise?.reject(notifyError);
|
|
1006
|
+
}
|
|
939
1007
|
}
|
|
940
1008
|
}, notifyTransactionId);
|
|
941
1009
|
|
|
@@ -943,13 +1011,18 @@ export default class ReactNativeBleTransport {
|
|
|
943
1011
|
}
|
|
944
1012
|
|
|
945
1013
|
async release(uuid: string, onclose = false) {
|
|
1014
|
+
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
1015
|
+
return this.releaseNative(uuid, onclose);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
private async releaseNative(uuid: string, onclose = false) {
|
|
946
1019
|
const transport = transportCache[uuid];
|
|
947
|
-
if (this.runPromise) {
|
|
1020
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
948
1021
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
1022
|
this.runPromise.reject(error);
|
|
950
1023
|
this.runPromise = null;
|
|
951
|
-
this.
|
|
952
|
-
this.
|
|
1024
|
+
this.runPromiseDeviceId = null;
|
|
1025
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
953
1026
|
} else {
|
|
954
1027
|
this.resetProtocolV2Frames(uuid);
|
|
955
1028
|
}
|
|
@@ -957,9 +1030,6 @@ export default class ReactNativeBleTransport {
|
|
|
957
1030
|
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
1031
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
1032
|
this.resetProtocolV2Frames(uuid);
|
|
960
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
-
this.activeProtocolV2Call = null;
|
|
962
|
-
}
|
|
963
1033
|
return Promise.resolve(true);
|
|
964
1034
|
}
|
|
965
1035
|
|
|
@@ -993,7 +1063,7 @@ export default class ReactNativeBleTransport {
|
|
|
993
1063
|
}
|
|
994
1064
|
|
|
995
1065
|
this.deviceProtocol.delete(uuid);
|
|
996
|
-
|
|
1066
|
+
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
997
1067
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
1068
|
this.protocolV2Assemblers.delete(uuid);
|
|
999
1069
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1025,13 +1095,6 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1095
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1026
1096
|
}
|
|
1027
1097
|
|
|
1028
|
-
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1029
|
-
|
|
1030
|
-
Log?.debug('transport-react-native call this.runPromise', this.runPromise);
|
|
1031
|
-
if (this.runPromise && !forceRun) {
|
|
1032
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
1098
|
const protocol = this.getProtocolType(uuid);
|
|
1036
1099
|
if (!protocol) {
|
|
1037
1100
|
throw ERRORS.TypedError(
|
|
@@ -1039,31 +1102,17 @@ export default class ReactNativeBleTransport {
|
|
|
1039
1102
|
`Device protocol has not been detected for ${uuid}`
|
|
1040
1103
|
);
|
|
1041
1104
|
}
|
|
1042
|
-
|
|
1043
|
-
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
1044
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
|
|
1045
|
-
file_name: data?.file_name,
|
|
1046
|
-
hash: data?.hash,
|
|
1047
|
-
});
|
|
1048
|
-
} else if (LogBlockCommand.has(name)) {
|
|
1049
|
-
Log?.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
|
|
1050
|
-
} else {
|
|
1051
|
-
Log?.debug(
|
|
1052
|
-
'transport-react-native',
|
|
1053
|
-
'call-',
|
|
1054
|
-
' name: ',
|
|
1055
|
-
name,
|
|
1056
|
-
' data: ',
|
|
1057
|
-
data,
|
|
1058
|
-
' protocol: ',
|
|
1059
|
-
protocol
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1105
|
+
Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
1062
1106
|
|
|
1063
1107
|
if (protocol === 'V2') {
|
|
1064
1108
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1065
1109
|
}
|
|
1066
1110
|
|
|
1111
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1112
|
+
if (this.runPromise && !forceRun) {
|
|
1113
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1067
1116
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
1117
|
}
|
|
1069
1118
|
|
|
@@ -1081,6 +1130,7 @@ export default class ReactNativeBleTransport {
|
|
|
1081
1130
|
const runPromise = createDeferred<string>();
|
|
1082
1131
|
runPromise.promise.catch(() => undefined);
|
|
1083
1132
|
this.runPromise = runPromise;
|
|
1133
|
+
this.runPromiseDeviceId = uuid;
|
|
1084
1134
|
const messages = this._messages;
|
|
1085
1135
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1086
1136
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1157,14 +1207,13 @@ export default class ReactNativeBleTransport {
|
|
|
1157
1207
|
}
|
|
1158
1208
|
);
|
|
1159
1209
|
} else if (name === 'FirmwareUpload') {
|
|
1160
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
1210
|
+
Log?.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
|
|
1161
1211
|
packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
|
|
1162
1212
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1163
1213
|
pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1164
1214
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1165
1215
|
maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
|
|
1166
1216
|
});
|
|
1167
|
-
|
|
1168
1217
|
await writeFirmwareUploadChunkedData(
|
|
1169
1218
|
buffers,
|
|
1170
1219
|
async data => {
|
|
@@ -1174,38 +1223,21 @@ export default class ReactNativeBleTransport {
|
|
|
1174
1223
|
// eslint-disable-next-line no-constant-condition
|
|
1175
1224
|
while (true) {
|
|
1176
1225
|
try {
|
|
1177
|
-
await transport.
|
|
1226
|
+
await transport.writeWithRetry(data);
|
|
1178
1227
|
return;
|
|
1179
1228
|
} catch (error) {
|
|
1180
1229
|
const retryType = getFirmwareUploadWriteRetryType(error);
|
|
1181
1230
|
if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1182
1231
|
throw error;
|
|
1183
1232
|
}
|
|
1184
|
-
const
|
|
1185
|
-
const delayMs = shouldReconnect
|
|
1186
|
-
? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
|
|
1187
|
-
: resolveFirmwareUploadRetryDelay(attempt);
|
|
1233
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1188
1234
|
Log?.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
|
|
1189
1235
|
attempt: attempt + 1,
|
|
1190
1236
|
delayMs,
|
|
1191
|
-
reconnect: shouldReconnect,
|
|
1192
1237
|
error,
|
|
1193
1238
|
});
|
|
1194
|
-
if (shouldReconnect) {
|
|
1195
|
-
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
1196
|
-
}
|
|
1197
1239
|
await delay(delayMs);
|
|
1198
1240
|
attempt += 1;
|
|
1199
|
-
if (shouldReconnect) {
|
|
1200
|
-
try {
|
|
1201
|
-
await this.reconnectFirmwareUploadTransport(uuid, transport);
|
|
1202
|
-
} catch (e) {
|
|
1203
|
-
Log?.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
|
|
1204
|
-
if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1205
|
-
throw e;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
1241
|
}
|
|
1210
1242
|
}
|
|
1211
1243
|
},
|
|
@@ -1218,9 +1250,14 @@ export default class ReactNativeBleTransport {
|
|
|
1218
1250
|
for (const o of buffers) {
|
|
1219
1251
|
const outData = o.toString('base64');
|
|
1220
1252
|
// Upload resources on low-end phones may OOM
|
|
1221
|
-
// this.Log.debug('send hex strting: ', o.toString('hex'));
|
|
1222
1253
|
try {
|
|
1223
|
-
|
|
1254
|
+
const shouldUseWriteWithResponse =
|
|
1255
|
+
Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1256
|
+
if (shouldUseWriteWithResponse) {
|
|
1257
|
+
await transport.writeCharacteristic.writeWithResponse(outData);
|
|
1258
|
+
} else {
|
|
1259
|
+
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1260
|
+
}
|
|
1224
1261
|
} catch (e) {
|
|
1225
1262
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1226
1263
|
this.runPromise = null;
|
|
@@ -1256,20 +1293,28 @@ export default class ReactNativeBleTransport {
|
|
|
1256
1293
|
throw new Error('Returning data is not string.');
|
|
1257
1294
|
}
|
|
1258
1295
|
|
|
1259
|
-
Log?.debug('receive data: ', response);
|
|
1260
1296
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1261
1297
|
return check.call(jsonData);
|
|
1262
1298
|
} catch (e) {
|
|
1263
|
-
if (name === '
|
|
1264
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1299
|
+
if (name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1300
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
|
|
1265
1301
|
} else {
|
|
1266
1302
|
Log?.error('call error: ', e);
|
|
1267
1303
|
}
|
|
1304
|
+
const isProbeTimeout =
|
|
1305
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1306
|
+
if (
|
|
1307
|
+
!isProbeTimeout &&
|
|
1308
|
+
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1309
|
+
) {
|
|
1310
|
+
await this.disconnect(uuid);
|
|
1311
|
+
}
|
|
1268
1312
|
throw e;
|
|
1269
1313
|
} finally {
|
|
1270
1314
|
if (timeout) clearTimeout(timeout);
|
|
1271
1315
|
if (this.runPromise === runPromise) {
|
|
1272
1316
|
this.runPromise = null;
|
|
1317
|
+
this.runPromiseDeviceId = null;
|
|
1273
1318
|
}
|
|
1274
1319
|
}
|
|
1275
1320
|
}
|
|
@@ -1279,8 +1324,9 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1324
|
}
|
|
1280
1325
|
|
|
1281
1326
|
async disconnect(session: string) {
|
|
1282
|
-
|
|
1327
|
+
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1283
1328
|
const transport = transportCache[session];
|
|
1329
|
+
const monitorToken = transport?.monitorToken ?? this.monitorTokens.get(session);
|
|
1284
1330
|
|
|
1285
1331
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
1286
1332
|
// from being triggered when we cancel the connection below
|
|
@@ -1341,20 +1387,16 @@ export default class ReactNativeBleTransport {
|
|
|
1341
1387
|
this.deviceProtocolHints.delete(session);
|
|
1342
1388
|
this.protocolV2Assemblers.delete(session);
|
|
1343
1389
|
this.resetProtocolV2Frames(session);
|
|
1344
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
-
this.activeProtocolV2Call = null;
|
|
1346
|
-
}
|
|
1347
1390
|
|
|
1348
1391
|
// emit the disconnect event
|
|
1349
1392
|
try {
|
|
1350
|
-
this.
|
|
1351
|
-
name: transport?.device?.name,
|
|
1352
|
-
id: session,
|
|
1353
|
-
connectId: session,
|
|
1354
|
-
});
|
|
1393
|
+
this.emitDeviceDisconnect(session, transport?.device?.name, monitorToken);
|
|
1355
1394
|
} catch (e) {
|
|
1356
1395
|
Log?.error('resetSession: emit disconnect event error: ', e);
|
|
1357
1396
|
}
|
|
1397
|
+
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1398
|
+
this.monitorTokens.delete(session);
|
|
1399
|
+
}
|
|
1358
1400
|
// eslint-disable-next-line no-promise-executor-return
|
|
1359
1401
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
|
1360
1402
|
}
|
|
@@ -1365,6 +1407,7 @@ export default class ReactNativeBleTransport {
|
|
|
1365
1407
|
// this.runPromise.reject(new Error('Transport_CallCanceled'));
|
|
1366
1408
|
}
|
|
1367
1409
|
this.runPromise = null;
|
|
1410
|
+
this.runPromiseDeviceId = null;
|
|
1368
1411
|
}
|
|
1369
1412
|
|
|
1370
1413
|
private getCachedTransport(uuid: string) {
|
|
@@ -1385,7 +1428,7 @@ export default class ReactNativeBleTransport {
|
|
|
1385
1428
|
private createProtocolDetectionError() {
|
|
1386
1429
|
return ERRORS.TypedError(
|
|
1387
1430
|
HardwareErrorCode.BleTimeoutError,
|
|
1388
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1431
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
1389
1432
|
);
|
|
1390
1433
|
}
|
|
1391
1434
|
|
|
@@ -1398,42 +1441,71 @@ export default class ReactNativeBleTransport {
|
|
|
1398
1441
|
private async detectProtocol(
|
|
1399
1442
|
uuid: string,
|
|
1400
1443
|
expectedProtocol?: ProtocolType,
|
|
1401
|
-
protocolHint?: ProtocolType
|
|
1444
|
+
protocolHint?: ProtocolType,
|
|
1445
|
+
rebuildTransport?: () => Promise<void>
|
|
1402
1446
|
): Promise<ProtocolType> {
|
|
1447
|
+
if (Platform.OS === 'ios' && expectedProtocol) {
|
|
1448
|
+
this.deviceProtocol.set(uuid, expectedProtocol);
|
|
1449
|
+
Log?.debug('[ReactNativeBleTransport] protocol selected', {
|
|
1450
|
+
deviceId: uuid,
|
|
1451
|
+
protocol: expectedProtocol,
|
|
1452
|
+
source: 'expected',
|
|
1453
|
+
});
|
|
1454
|
+
return expectedProtocol;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1403
1457
|
if (expectedProtocol === 'V1') {
|
|
1404
1458
|
if (await this.probeProtocolV1(uuid)) {
|
|
1405
1459
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
-
Log?.debug(
|
|
1460
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1461
|
+
deviceId: uuid,
|
|
1462
|
+
protocol: 'V1',
|
|
1463
|
+
source: 'expected',
|
|
1464
|
+
});
|
|
1407
1465
|
return 'V1';
|
|
1408
1466
|
}
|
|
1409
1467
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
1468
|
}
|
|
1411
1469
|
|
|
1412
1470
|
if (expectedProtocol === 'V2') {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1471
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
1472
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1473
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1474
|
+
deviceId: uuid,
|
|
1475
|
+
protocol: 'V2',
|
|
1476
|
+
source: 'expected',
|
|
1477
|
+
});
|
|
1478
|
+
return 'V2';
|
|
1479
|
+
}
|
|
1480
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1418
1481
|
}
|
|
1419
1482
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 不能作为最终结论。
|
|
1483
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1484
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1423
1485
|
const probeOrder: ProtocolType[] =
|
|
1424
1486
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
1487
|
|
|
1426
1488
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
1489
|
const protocol = probeOrder[i];
|
|
1428
1490
|
if (i > 0) {
|
|
1429
|
-
//
|
|
1491
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1430
1492
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1493
|
+
if (!transportCache[uuid]) {
|
|
1494
|
+
if (!rebuildTransport) {
|
|
1495
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1496
|
+
}
|
|
1497
|
+
await rebuildTransport();
|
|
1498
|
+
}
|
|
1431
1499
|
}
|
|
1432
1500
|
const detected =
|
|
1433
1501
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
1502
|
if (detected) {
|
|
1435
1503
|
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
-
Log?.debug(
|
|
1504
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1505
|
+
deviceId: uuid,
|
|
1506
|
+
protocol,
|
|
1507
|
+
source: 'probe',
|
|
1508
|
+
});
|
|
1437
1509
|
return protocol;
|
|
1438
1510
|
}
|
|
1439
1511
|
}
|
|
@@ -1444,11 +1516,12 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1516
|
|
|
1445
1517
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
1518
|
const transport = transportCache[uuid];
|
|
1519
|
+
await this.protocolV2Links.invalidateLink(
|
|
1520
|
+
uuid,
|
|
1521
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
1522
|
+
);
|
|
1447
1523
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
1524
|
this.resetProtocolV2Frames(uuid);
|
|
1449
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
-
this.activeProtocolV2Call = null;
|
|
1451
|
-
}
|
|
1452
1525
|
if (this.runPromise) {
|
|
1453
1526
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
1527
|
this.runPromise.reject(error);
|
|
@@ -1500,11 +1573,13 @@ export default class ReactNativeBleTransport {
|
|
|
1500
1573
|
|
|
1501
1574
|
try {
|
|
1502
1575
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1503
|
-
|
|
1576
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1577
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
1578
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1504
1579
|
return true;
|
|
1505
1580
|
} catch (error) {
|
|
1506
1581
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1507
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1582
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1508
1583
|
return false;
|
|
1509
1584
|
}
|
|
1510
1585
|
}
|
|
@@ -1533,13 +1608,9 @@ export default class ReactNativeBleTransport {
|
|
|
1533
1608
|
return detected;
|
|
1534
1609
|
}
|
|
1535
1610
|
|
|
1536
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1611
|
+
private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
|
|
1537
1612
|
try {
|
|
1538
|
-
if (
|
|
1539
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
-
this.resetProtocolV2Frames(uuid);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1613
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) return;
|
|
1543
1614
|
|
|
1544
1615
|
if (data.length === 0) return;
|
|
1545
1616
|
|
|
@@ -1552,8 +1623,15 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1623
|
} catch (error) {
|
|
1553
1624
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
1625
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1626
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1627
|
+
this.protocolV2Links
|
|
1628
|
+
.invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
|
|
1629
|
+
.catch(invalidateError =>
|
|
1630
|
+
Log?.debug(
|
|
1631
|
+
'[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
|
|
1632
|
+
invalidateError
|
|
1633
|
+
)
|
|
1634
|
+
);
|
|
1557
1635
|
}
|
|
1558
1636
|
}
|
|
1559
1637
|
|
|
@@ -1576,21 +1654,17 @@ export default class ReactNativeBleTransport {
|
|
|
1576
1654
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1577
1655
|
}
|
|
1578
1656
|
|
|
1579
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
1580
|
-
this.protocolV2FrameQueues.clear();
|
|
1581
|
-
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1582
|
-
framePromise.reject(error);
|
|
1583
|
-
}
|
|
1584
|
-
this.protocolV2FramePromises.clear();
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
1657
|
private resetProtocolV2Frames(uuid: string) {
|
|
1588
|
-
this.
|
|
1589
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1658
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1590
1659
|
}
|
|
1591
1660
|
|
|
1592
|
-
private
|
|
1593
|
-
|
|
1661
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
1662
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1663
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1664
|
+
if (framePromise) {
|
|
1665
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1666
|
+
framePromise.reject(error);
|
|
1667
|
+
}
|
|
1594
1668
|
}
|
|
1595
1669
|
|
|
1596
1670
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -1610,10 +1684,53 @@ export default class ReactNativeBleTransport {
|
|
|
1610
1684
|
}
|
|
1611
1685
|
}
|
|
1612
1686
|
|
|
1687
|
+
private async writeProtocolV2Packet(
|
|
1688
|
+
transport: BleTransport,
|
|
1689
|
+
base64: string,
|
|
1690
|
+
context: ProtocolV2CallContext,
|
|
1691
|
+
assertCurrentGeneration: () => void
|
|
1692
|
+
) {
|
|
1693
|
+
const shouldUseWriteWithResponse =
|
|
1694
|
+
transport.writeCharacteristic.isWritableWithResponse &&
|
|
1695
|
+
(context.writeWithResponse === true || (Platform.OS === 'ios' && !context.highVolume));
|
|
1696
|
+
let attempt = 0;
|
|
1697
|
+
for (;;) {
|
|
1698
|
+
assertCurrentGeneration();
|
|
1699
|
+
if (context.signal.aborted) {
|
|
1700
|
+
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1701
|
+
}
|
|
1702
|
+
try {
|
|
1703
|
+
if (shouldUseWriteWithResponse) {
|
|
1704
|
+
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1705
|
+
} else {
|
|
1706
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1707
|
+
}
|
|
1708
|
+
assertCurrentGeneration();
|
|
1709
|
+
return;
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
if (
|
|
1712
|
+
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
1713
|
+
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|
|
1714
|
+
) {
|
|
1715
|
+
throw error;
|
|
1716
|
+
}
|
|
1717
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1718
|
+
attempt += 1;
|
|
1719
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
|
|
1720
|
+
name: context.messageName,
|
|
1721
|
+
attempt,
|
|
1722
|
+
delayMs,
|
|
1723
|
+
});
|
|
1724
|
+
await delay(delayMs);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1613
1729
|
private async writeProtocolV2Frame(
|
|
1614
1730
|
transport: BleTransport,
|
|
1615
1731
|
frame: Uint8Array,
|
|
1616
|
-
|
|
1732
|
+
context: ProtocolV2CallContext,
|
|
1733
|
+
assertCurrentGeneration: () => void
|
|
1617
1734
|
) {
|
|
1618
1735
|
const tuning = getProtocolV2BleTuning();
|
|
1619
1736
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
@@ -1622,37 +1739,31 @@ export default class ReactNativeBleTransport {
|
|
|
1622
1739
|
androidPacketLength: tuning.androidPacketLength,
|
|
1623
1740
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
1741
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
if (shouldThrottle) {
|
|
1654
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1655
|
-
}
|
|
1742
|
+
// Match Desktop BLE pacing so Pro2 firmware can finish the previous response
|
|
1743
|
+
// before the next single-packet control command is written.
|
|
1744
|
+
const initialDelayMs =
|
|
1745
|
+
Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
1746
|
+
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
1747
|
+
: 0;
|
|
1748
|
+
await writeProtocolV2BleFrame({
|
|
1749
|
+
frame,
|
|
1750
|
+
packetCapacity,
|
|
1751
|
+
assertActive: assertCurrentGeneration,
|
|
1752
|
+
signal: context.signal,
|
|
1753
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1754
|
+
initialDelayMs,
|
|
1755
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1756
|
+
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1757
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1758
|
+
wait: delay,
|
|
1759
|
+
writePacket: packet =>
|
|
1760
|
+
this.writeProtocolV2Packet(
|
|
1761
|
+
transport,
|
|
1762
|
+
Buffer.from(packet).toString('base64'),
|
|
1763
|
+
context,
|
|
1764
|
+
assertCurrentGeneration
|
|
1765
|
+
),
|
|
1766
|
+
});
|
|
1656
1767
|
}
|
|
1657
1768
|
|
|
1658
1769
|
private async callProtocolV2(
|
|
@@ -1665,101 +1776,76 @@ export default class ReactNativeBleTransport {
|
|
|
1665
1776
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1666
1777
|
}
|
|
1667
1778
|
|
|
1668
|
-
const
|
|
1669
|
-
if (this.runPromise) {
|
|
1670
|
-
if (!forceRun) {
|
|
1671
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1672
|
-
}
|
|
1673
|
-
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1674
|
-
this.runPromise.reject(error);
|
|
1675
|
-
this.rejectAllProtocolV2Frames(error);
|
|
1676
|
-
this.runPromise = null;
|
|
1677
|
-
this.activeProtocolV2Call = null;
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
const transport = this.getCachedTransport(uuid);
|
|
1681
|
-
const runPromise = createDeferred<Uint8Array>();
|
|
1682
|
-
runPromise.promise.catch(() => undefined);
|
|
1683
|
-
this.runPromise = runPromise;
|
|
1684
|
-
const callToken = this.nextProtocolV2CallToken++;
|
|
1685
|
-
this.activeProtocolV2Call = { uuid, token: callToken };
|
|
1686
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1687
|
-
this.resetProtocolV2Frames(uuid);
|
|
1688
|
-
let completed = false;
|
|
1689
|
-
const callOptions = {
|
|
1690
|
-
...options,
|
|
1691
|
-
timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
|
|
1692
|
-
};
|
|
1779
|
+
const callOptions = options;
|
|
1693
1780
|
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1694
1781
|
|
|
1695
1782
|
if (highVolumeWrite) {
|
|
1696
1783
|
const tuning = getProtocolV2BleTuning();
|
|
1697
|
-
Log?.debug(
|
|
1698
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1784
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1699
1785
|
name,
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1704
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1705
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1706
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1707
|
-
}
|
|
1708
|
-
);
|
|
1786
|
+
writeMode: options?.writeWithResponse ? 'withResponse' : 'withoutResponse',
|
|
1787
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1788
|
+
});
|
|
1709
1789
|
}
|
|
1710
1790
|
|
|
1711
1791
|
try {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
await this.writeProtocolV2Frame(transport, frame, {
|
|
1720
|
-
highVolume: highVolumeWrite,
|
|
1721
|
-
});
|
|
1722
|
-
},
|
|
1723
|
-
readFrame: async () => {
|
|
1724
|
-
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1725
|
-
if (!(rxFrame instanceof Uint8Array)) {
|
|
1726
|
-
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1727
|
-
}
|
|
1728
|
-
return rxFrame;
|
|
1729
|
-
},
|
|
1730
|
-
logger: Log,
|
|
1731
|
-
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1732
|
-
createTimeoutError: (_messageName: string, timeout: number) =>
|
|
1733
|
-
ERRORS.TypedError(
|
|
1734
|
-
HardwareErrorCode.BleTimeoutError,
|
|
1735
|
-
`BLE response timeout after ${timeout}ms for ${name}`
|
|
1736
|
-
),
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
|
-
const result = await session.call(name, data, callOptions);
|
|
1740
|
-
completed = true;
|
|
1741
|
-
return result;
|
|
1792
|
+
return await this.protocolV2Links.call(
|
|
1793
|
+
uuid,
|
|
1794
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
1795
|
+
name,
|
|
1796
|
+
data,
|
|
1797
|
+
callOptions
|
|
1798
|
+
);
|
|
1742
1799
|
} catch (e) {
|
|
1743
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1744
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1745
|
-
this.resetProtocolV2Frames(uuid);
|
|
1746
|
-
}
|
|
1747
1800
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1748
1801
|
throw e;
|
|
1749
|
-
} finally {
|
|
1750
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1751
|
-
if (!completed) {
|
|
1752
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1753
|
-
}
|
|
1754
|
-
this.resetProtocolV2Frames(uuid);
|
|
1755
|
-
this.activeProtocolV2Call = null;
|
|
1756
|
-
}
|
|
1757
|
-
if (this.runPromise === runPromise) {
|
|
1758
|
-
this.runPromise = null;
|
|
1759
|
-
}
|
|
1760
1802
|
}
|
|
1761
1803
|
}
|
|
1762
1804
|
|
|
1805
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
1806
|
+
const generation = this.monitorTokens.get(uuid) ?? 0;
|
|
1807
|
+
const assertCurrentGeneration = () => {
|
|
1808
|
+
if (this.monitorTokens.get(uuid) !== generation) {
|
|
1809
|
+
throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
return {
|
|
1814
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1815
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1816
|
+
generation,
|
|
1817
|
+
prepareCall: () => {
|
|
1818
|
+
assertCurrentGeneration();
|
|
1819
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1820
|
+
this.resetProtocolV2Frames(uuid);
|
|
1821
|
+
},
|
|
1822
|
+
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
1823
|
+
assertCurrentGeneration();
|
|
1824
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1825
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
1826
|
+
},
|
|
1827
|
+
readFrame: async () => {
|
|
1828
|
+
assertCurrentGeneration();
|
|
1829
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1830
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1831
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1832
|
+
}
|
|
1833
|
+
return rxFrame;
|
|
1834
|
+
},
|
|
1835
|
+
reset: (reason: string) => {
|
|
1836
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1837
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1838
|
+
},
|
|
1839
|
+
logger: Log,
|
|
1840
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1841
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
1842
|
+
ERRORS.TypedError(
|
|
1843
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1844
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
1845
|
+
),
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1763
1849
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
1764
1850
|
return this.deviceProtocol.get(path);
|
|
1765
1851
|
}
|