@onekeyfe/hd-transport-react-native 1.2.0-alpha.5 → 1.2.0-alpha.51
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 +379 -372
- 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 +10 -0
- package/package.json +7 -6
- package/src/BleTransport.ts +2 -43
- package/src/__tests__/BleTransport.test.ts +41 -0
- package/src/__tests__/bleStrategy.test.ts +1 -6
- package/src/__tests__/constants.test.ts +20 -0
- package/src/__tests__/protocolV2Link.test.ts +520 -0
- package/src/__tests__/staleCallTimeout.test.ts +211 -0
- package/src/bleStrategy.ts +0 -25
- package/src/constants.ts +7 -13
- package/src/index.ts +452 -391
- 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,30 @@ 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
|
+
} from '@onekeyfe/hd-shared';
|
|
23
32
|
|
|
24
33
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
25
|
-
import {
|
|
26
|
-
hasWritableCapability,
|
|
27
|
-
resolveBleWriteMode,
|
|
28
|
-
resolveProtocolV2PacketCapacity,
|
|
29
|
-
} from './bleStrategy';
|
|
34
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
30
35
|
import { subscribeBleOn } from './subscribeBleOn';
|
|
31
36
|
import {
|
|
32
37
|
ANDROID_PACKET_LENGTH,
|
|
33
38
|
IOS_PACKET_LENGTH,
|
|
34
|
-
getBleUuidKey,
|
|
35
39
|
getBluetoothServiceUuids,
|
|
36
40
|
getInfosForServiceUuid,
|
|
37
41
|
isSameBleUuid,
|
|
@@ -40,6 +44,7 @@ import { isHeaderChunk } from './utils/validateNotify';
|
|
|
40
44
|
import BleTransport from './BleTransport';
|
|
41
45
|
import timer from './utils/timer';
|
|
42
46
|
import { bleLogger, setBleLogger } from './logger';
|
|
47
|
+
import { createTransportCallLog } from './transportLog';
|
|
43
48
|
|
|
44
49
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
45
50
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
@@ -55,13 +60,12 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
55
60
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
56
61
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
57
62
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
58
|
-
const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
|
|
59
63
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
60
64
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
61
65
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
62
66
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
63
67
|
|
|
64
|
-
type FirmwareUploadWriteRetryType = 'congested'
|
|
68
|
+
type FirmwareUploadWriteRetryType = 'congested';
|
|
65
69
|
type ResolvedBleCharacteristics = {
|
|
66
70
|
writeCharacteristic: Characteristic;
|
|
67
71
|
notifyCharacteristic: Characteristic;
|
|
@@ -72,7 +76,9 @@ const delay = (ms: number) =>
|
|
|
72
76
|
setTimeout(resolve, ms);
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
const getFirmwareUploadWriteRetryType = (
|
|
79
|
+
export const getFirmwareUploadWriteRetryType = (
|
|
80
|
+
error: unknown
|
|
81
|
+
): FirmwareUploadWriteRetryType | null => {
|
|
76
82
|
if (!error || typeof error !== 'object') return null;
|
|
77
83
|
const bleWriteError = error as {
|
|
78
84
|
androidErrorCode?: unknown;
|
|
@@ -83,13 +89,6 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
83
89
|
name?: unknown;
|
|
84
90
|
};
|
|
85
91
|
|
|
86
|
-
if (
|
|
87
|
-
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
88
|
-
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
89
|
-
) {
|
|
90
|
-
return 'reconnectable';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
92
|
if (
|
|
94
93
|
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
95
94
|
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
@@ -105,23 +104,14 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
105
104
|
|
|
106
105
|
const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
|
|
107
106
|
Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
108
|
-
const BLE_RESPONSE_TIMEOUT_MS = 30_000;
|
|
109
107
|
const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
|
|
110
108
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
111
|
-
const DEVICE_SCAN_TIMEOUT_MS =
|
|
109
|
+
const DEVICE_SCAN_TIMEOUT_MS = 3000;
|
|
112
110
|
const IOS_NOTIFY_READY_DELAY_MS = 150;
|
|
113
111
|
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
112
|
export type ProtocolV2BleTuning = {
|
|
119
113
|
iosPacketLength?: number;
|
|
120
114
|
androidPacketLength?: number;
|
|
121
|
-
highVolumeWriteBurstSize?: number;
|
|
122
|
-
highVolumeWritePauseMs?: number;
|
|
123
|
-
highVolumeWriteFlushDelayMs?: number;
|
|
124
|
-
highVolumeWriteWithResponse?: boolean;
|
|
125
115
|
};
|
|
126
116
|
|
|
127
117
|
type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
@@ -129,10 +119,6 @@ type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
|
|
|
129
119
|
const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
|
|
130
120
|
iosPacketLength: IOS_PACKET_LENGTH,
|
|
131
121
|
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
122
|
};
|
|
137
123
|
|
|
138
124
|
let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
@@ -153,27 +139,13 @@ export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
|
|
|
153
139
|
tuning.androidPacketLength,
|
|
154
140
|
protocolV2BleTuning.androidPacketLength
|
|
155
141
|
),
|
|
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
142
|
};
|
|
171
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
143
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
|
|
172
144
|
}
|
|
173
145
|
|
|
174
146
|
export function resetProtocolV2BleTuning() {
|
|
175
147
|
protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
|
|
176
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
148
|
+
Log?.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
|
|
177
149
|
}
|
|
178
150
|
|
|
179
151
|
export function getProtocolV2BleTuning() {
|
|
@@ -188,16 +160,6 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
188
160
|
return device?.name || device?.localName || null;
|
|
189
161
|
}
|
|
190
162
|
|
|
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
163
|
const ANDROID_REQUEST_MTU = 256;
|
|
202
164
|
|
|
203
165
|
const connectOptions: Record<string, unknown> = {
|
|
@@ -222,9 +184,10 @@ const requestAndroidMtu = async (device: Device) => {
|
|
|
222
184
|
|
|
223
185
|
try {
|
|
224
186
|
const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
|
|
225
|
-
Log?.debug('[ReactNativeBleTransport]
|
|
187
|
+
Log?.debug('[ReactNativeBleTransport] MTU configured', {
|
|
188
|
+
deviceId: device.id,
|
|
226
189
|
requested: ANDROID_REQUEST_MTU,
|
|
227
|
-
|
|
190
|
+
actual: mtuDevice.mtu,
|
|
228
191
|
});
|
|
229
192
|
return mtuDevice;
|
|
230
193
|
} catch (error) {
|
|
@@ -272,6 +235,8 @@ export default class ReactNativeBleTransport {
|
|
|
272
235
|
|
|
273
236
|
_messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
274
237
|
|
|
238
|
+
private protocolV2SchemaConfiguration: string | undefined;
|
|
239
|
+
|
|
275
240
|
name = 'ReactNativeBleTransport';
|
|
276
241
|
|
|
277
242
|
configured = false;
|
|
@@ -282,6 +247,8 @@ export default class ReactNativeBleTransport {
|
|
|
282
247
|
|
|
283
248
|
runPromise: Deferred<any> | null = null;
|
|
284
249
|
|
|
250
|
+
private runPromiseDeviceId: string | null = null;
|
|
251
|
+
|
|
285
252
|
emitter?: EventEmitter;
|
|
286
253
|
|
|
287
254
|
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
@@ -297,12 +264,31 @@ export default class ReactNativeBleTransport {
|
|
|
297
264
|
|
|
298
265
|
private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
|
|
299
266
|
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
267
|
+
private protocolV2Links = new ProtocolV2LinkManager<string>({
|
|
268
|
+
getSchemas: () => {
|
|
269
|
+
if (!this._messages || !this._messagesV2) {
|
|
270
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
protocolV1: this._messages,
|
|
274
|
+
protocolV2: this._messagesV2,
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
classifyError: () => 'link-fatal',
|
|
278
|
+
onLinkInvalidated: async (uuid, reason) => {
|
|
279
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
280
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
281
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
|
|
282
|
+
if (reason.startsWith('Protocol V2 link-fatal error:')) {
|
|
283
|
+
await this.releaseNative(uuid, true);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
});
|
|
303
287
|
|
|
304
288
|
private monitorTokens: Map<string, number> = new Map();
|
|
305
289
|
|
|
290
|
+
private disconnectEventTokens: Map<string, number> = new Map();
|
|
291
|
+
|
|
306
292
|
private nextMonitorToken = 1;
|
|
307
293
|
|
|
308
294
|
constructor(options: TransportOptions) {
|
|
@@ -321,8 +307,19 @@ export default class ReactNativeBleTransport {
|
|
|
321
307
|
}
|
|
322
308
|
|
|
323
309
|
configureProtocolV2(signedData: any) {
|
|
310
|
+
const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
|
|
311
|
+
if (this.protocolV2SchemaConfiguration === configuration) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
|
|
324
316
|
this._messagesV2 = parseConfigure(signedData);
|
|
325
|
-
|
|
317
|
+
this.protocolV2SchemaConfiguration = configuration;
|
|
318
|
+
if (isReconfiguration) {
|
|
319
|
+
this.protocolV2Links
|
|
320
|
+
.invalidateAllLinks('Protocol V2 schema reconfigured')
|
|
321
|
+
.catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
|
|
322
|
+
}
|
|
326
323
|
}
|
|
327
324
|
|
|
328
325
|
listen() {
|
|
@@ -352,29 +349,15 @@ export default class ReactNativeBleTransport {
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
|
|
355
|
-
let fallbackServiceUuid: string | undefined;
|
|
356
|
-
|
|
357
352
|
if (!infos) {
|
|
358
353
|
const services = await device.services();
|
|
359
354
|
Log?.debug(
|
|
360
355
|
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
361
356
|
services?.map(service => service.uuid)
|
|
362
357
|
);
|
|
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
358
|
}
|
|
376
359
|
|
|
377
|
-
if (!infos
|
|
360
|
+
if (!infos) {
|
|
378
361
|
try {
|
|
379
362
|
Log?.debug('cancel connection when service not found');
|
|
380
363
|
await device.cancelConnection();
|
|
@@ -384,9 +367,7 @@ export default class ReactNativeBleTransport {
|
|
|
384
367
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
385
368
|
}
|
|
386
369
|
|
|
387
|
-
const serviceUuid = infos
|
|
388
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
389
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
370
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
390
371
|
|
|
391
372
|
if (!serviceUuid) {
|
|
392
373
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
@@ -436,6 +417,7 @@ export default class ReactNativeBleTransport {
|
|
|
436
417
|
|
|
437
418
|
attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
|
|
438
419
|
transport.disconnectSubscription?.remove();
|
|
420
|
+
const { monitorToken } = transport;
|
|
439
421
|
transport.disconnectSubscription = device.onDisconnected(() => {
|
|
440
422
|
if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
|
|
441
423
|
Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
|
|
@@ -445,18 +427,17 @@ export default class ReactNativeBleTransport {
|
|
|
445
427
|
Log?.debug('device disconnect ignored for stale transport: ', device?.id);
|
|
446
428
|
return;
|
|
447
429
|
}
|
|
430
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) {
|
|
431
|
+
Log?.debug('device disconnect ignored for stale generation: ', device?.id);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
448
434
|
|
|
449
435
|
try {
|
|
450
436
|
Log?.debug('device disconnect: ', device?.id);
|
|
451
|
-
this.
|
|
452
|
-
|
|
453
|
-
id: device?.id,
|
|
454
|
-
connectId: device?.id,
|
|
455
|
-
});
|
|
456
|
-
if (this.runPromise) {
|
|
437
|
+
this.emitDeviceDisconnect(uuid, device?.name, monitorToken);
|
|
438
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
457
439
|
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
458
440
|
this.runPromise.reject(error);
|
|
459
|
-
this.rejectAllProtocolV2Frames(error);
|
|
460
441
|
}
|
|
461
442
|
} catch (e) {
|
|
462
443
|
Log?.debug('device disconnect error: ', e);
|
|
@@ -466,6 +447,22 @@ export default class ReactNativeBleTransport {
|
|
|
466
447
|
});
|
|
467
448
|
}
|
|
468
449
|
|
|
450
|
+
private emitDeviceDisconnect(uuid: string, name: string | null | undefined, token?: number) {
|
|
451
|
+
if (token === undefined || this.disconnectEventTokens.get(uuid) === token) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (this.monitorTokens.get(uuid) !== token) {
|
|
455
|
+
Log?.debug('device disconnect event ignored for stale generation: ', uuid);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.disconnectEventTokens.set(uuid, token);
|
|
459
|
+
this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
|
|
460
|
+
name,
|
|
461
|
+
id: uuid,
|
|
462
|
+
connectId: uuid,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
469
466
|
async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
|
|
470
467
|
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
471
468
|
try {
|
|
@@ -553,14 +550,13 @@ export default class ReactNativeBleTransport {
|
|
|
553
550
|
}
|
|
554
551
|
|
|
555
552
|
blePlxManager.startDeviceScan(
|
|
556
|
-
|
|
553
|
+
getBluetoothServiceUuids(),
|
|
557
554
|
{
|
|
558
555
|
allowDuplicates: true,
|
|
559
556
|
scanMode: ScanMode.LowLatency,
|
|
560
557
|
},
|
|
561
558
|
(error, device) => {
|
|
562
559
|
if (error) {
|
|
563
|
-
Log?.debug('ble scan manager: ', blePlxManager);
|
|
564
560
|
Log?.debug('ble scan error: ', error);
|
|
565
561
|
if (
|
|
566
562
|
[BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
|
|
@@ -584,33 +580,14 @@ export default class ReactNativeBleTransport {
|
|
|
584
580
|
}
|
|
585
581
|
|
|
586
582
|
const displayName = getDeviceDisplayName(device);
|
|
587
|
-
const isOneKey =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (shouldTraceCandidate) {
|
|
595
|
-
Log?.debug('[ReactNativeBleTransport] scan candidate', {
|
|
596
|
-
name: device?.name,
|
|
597
|
-
localName: device?.localName,
|
|
598
|
-
id: device?.id,
|
|
599
|
-
serviceUUIDs: device?.serviceUUIDs,
|
|
600
|
-
accepted: isOneKey,
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
583
|
+
const isOneKey = isOnekeyBluetoothDevice({
|
|
584
|
+
id: device?.id,
|
|
585
|
+
name: device?.name,
|
|
586
|
+
localName: device?.localName,
|
|
587
|
+
serviceUuids: device?.serviceUUIDs,
|
|
588
|
+
});
|
|
604
589
|
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
590
|
addDevice(device as unknown as Device);
|
|
613
|
-
Log?.debug('search device end ======================\n');
|
|
614
591
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
615
592
|
Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
|
|
616
593
|
name: device?.name,
|
|
@@ -622,12 +599,27 @@ export default class ReactNativeBleTransport {
|
|
|
622
599
|
}
|
|
623
600
|
);
|
|
624
601
|
|
|
625
|
-
getConnectedDeviceIds(getBluetoothServiceUuids()).then(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
602
|
+
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
603
|
+
devices => {
|
|
604
|
+
for (const device of devices) {
|
|
605
|
+
const localName =
|
|
606
|
+
'localName' in device && typeof device.localName === 'string'
|
|
607
|
+
? device.localName
|
|
608
|
+
: null;
|
|
609
|
+
if (
|
|
610
|
+
isOnekeyBluetoothDevice({
|
|
611
|
+
id: device.id,
|
|
612
|
+
name: device.name,
|
|
613
|
+
localName,
|
|
614
|
+
serviceUuids: device.serviceUUIDs,
|
|
615
|
+
})
|
|
616
|
+
) {
|
|
617
|
+
Log?.debug('search connected peripheral: ', device.id);
|
|
618
|
+
addDevice(device as unknown as Device);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
629
621
|
}
|
|
630
|
-
|
|
622
|
+
);
|
|
631
623
|
|
|
632
624
|
const addDevice = (device: Device) => {
|
|
633
625
|
if (deviceList.every(d => d.id !== device.id)) {
|
|
@@ -641,6 +633,12 @@ export default class ReactNativeBleTransport {
|
|
|
641
633
|
name: displayName,
|
|
642
634
|
commType: 'ble',
|
|
643
635
|
} as IOneKeyDevice);
|
|
636
|
+
Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
|
|
637
|
+
deviceId: device.id,
|
|
638
|
+
name: displayName,
|
|
639
|
+
serviceUUIDs: device.serviceUUIDs,
|
|
640
|
+
protocolHint,
|
|
641
|
+
});
|
|
644
642
|
}
|
|
645
643
|
};
|
|
646
644
|
|
|
@@ -651,6 +649,46 @@ export default class ReactNativeBleTransport {
|
|
|
651
649
|
});
|
|
652
650
|
}
|
|
653
651
|
|
|
652
|
+
private async installTransportForAcquire(
|
|
653
|
+
uuid: string,
|
|
654
|
+
device: Device,
|
|
655
|
+
characteristics?: ResolvedBleCharacteristics
|
|
656
|
+
) {
|
|
657
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
658
|
+
characteristics ?? (await this.resolveCharacteristics(device));
|
|
659
|
+
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
660
|
+
if (Platform.OS === 'android') {
|
|
661
|
+
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
662
|
+
}
|
|
663
|
+
const monitorToken = this.nextMonitorToken;
|
|
664
|
+
this.nextMonitorToken += 1;
|
|
665
|
+
const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
|
|
666
|
+
transport.monitorToken = monitorToken;
|
|
667
|
+
transport.notifyTransactionId = notifyTransactionId;
|
|
668
|
+
this.monitorTokens.set(uuid, monitorToken);
|
|
669
|
+
transport.notifySubscription = this._monitorCharacteristic(
|
|
670
|
+
transport.notifyCharacteristic,
|
|
671
|
+
uuid,
|
|
672
|
+
monitorToken,
|
|
673
|
+
notifyTransactionId
|
|
674
|
+
);
|
|
675
|
+
transportCache[uuid] = transport;
|
|
676
|
+
this.protocolV2Assemblers.set(
|
|
677
|
+
uuid,
|
|
678
|
+
new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
if (Platform.OS === 'ios') {
|
|
682
|
+
await new Promise<void>(resolve => {
|
|
683
|
+
setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
|
|
684
|
+
});
|
|
685
|
+
} else if (Platform.OS === 'android') {
|
|
686
|
+
await delay(ANDROID_NOTIFY_READY_DELAY_MS);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return transport;
|
|
690
|
+
}
|
|
691
|
+
|
|
654
692
|
async acquire(input: BleAcquireInput) {
|
|
655
693
|
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
656
694
|
|
|
@@ -684,9 +722,8 @@ export default class ReactNativeBleTransport {
|
|
|
684
722
|
if (forceCleanRunPromise && this.runPromise) {
|
|
685
723
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
686
724
|
this.runPromise.reject(error);
|
|
687
|
-
this.rejectAllProtocolV2Frames(error);
|
|
688
725
|
this.runPromise = null;
|
|
689
|
-
this.
|
|
726
|
+
this.runPromiseDeviceId = null;
|
|
690
727
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
691
728
|
}
|
|
692
729
|
|
|
@@ -775,12 +812,16 @@ export default class ReactNativeBleTransport {
|
|
|
775
812
|
}
|
|
776
813
|
|
|
777
814
|
device = await requestAndroidMtu(device);
|
|
778
|
-
const
|
|
815
|
+
const acquiredDevice = device;
|
|
816
|
+
const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
|
|
817
|
+
acquiredDevice
|
|
818
|
+
);
|
|
779
819
|
|
|
780
820
|
const protocolHint = expectedProtocol
|
|
781
821
|
? undefined
|
|
782
|
-
:
|
|
783
|
-
|
|
822
|
+
: input.protocolHint ??
|
|
823
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
824
|
+
inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
784
825
|
|
|
785
826
|
// release transport before new transport instance
|
|
786
827
|
await this.release(uuid, true);
|
|
@@ -788,45 +829,30 @@ export default class ReactNativeBleTransport {
|
|
|
788
829
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
789
830
|
}
|
|
790
831
|
|
|
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,
|
|
832
|
+
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
833
|
+
writeCharacteristic,
|
|
834
|
+
notifyCharacteristic,
|
|
825
835
|
});
|
|
826
836
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
837
|
+
try {
|
|
838
|
+
const protocolType = await this.detectProtocol(
|
|
839
|
+
uuid,
|
|
840
|
+
expectedProtocol,
|
|
841
|
+
protocolHint,
|
|
842
|
+
async () => {
|
|
843
|
+
await this.installTransportForAcquire(uuid, acquiredDevice);
|
|
844
|
+
}
|
|
845
|
+
);
|
|
846
|
+
const currentTransport = transportCache[uuid];
|
|
847
|
+
if (!currentTransport) {
|
|
848
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
849
|
+
}
|
|
850
|
+
this.attachDisconnectSubscription(currentTransport, acquiredDevice, uuid);
|
|
851
|
+
return { uuid, protocolType };
|
|
852
|
+
} catch (error) {
|
|
853
|
+
await this.release(uuid, true);
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
830
856
|
}
|
|
831
857
|
|
|
832
858
|
_monitorCharacteristic(
|
|
@@ -853,7 +879,30 @@ export default class ReactNativeBleTransport {
|
|
|
853
879
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
854
880
|
return;
|
|
855
881
|
}
|
|
856
|
-
if (this.
|
|
882
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
883
|
+
let errorCode:
|
|
884
|
+
| typeof HardwareErrorCode.BleDeviceBondError
|
|
885
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
886
|
+
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
887
|
+
| typeof HardwareErrorCode.BleTimeoutError =
|
|
888
|
+
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
889
|
+
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
890
|
+
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
891
|
+
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
892
|
+
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
893
|
+
} else if (
|
|
894
|
+
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
895
|
+
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
896
|
+
error.reason?.includes('The handle is invalid') ||
|
|
897
|
+
error.reason?.includes('Writing is not permitted') ||
|
|
898
|
+
error.reason?.includes('notify change failed for device')
|
|
899
|
+
) {
|
|
900
|
+
errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
901
|
+
}
|
|
902
|
+
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
857
906
|
let ERROR:
|
|
858
907
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
859
908
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -876,7 +925,6 @@ export default class ReactNativeBleTransport {
|
|
|
876
925
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
877
926
|
);
|
|
878
927
|
this.runPromise.reject(notifyError);
|
|
879
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
880
928
|
Log?.debug(
|
|
881
929
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
882
930
|
);
|
|
@@ -884,7 +932,6 @@ export default class ReactNativeBleTransport {
|
|
|
884
932
|
}
|
|
885
933
|
const notifyError = ERRORS.TypedError(ERROR);
|
|
886
934
|
this.runPromise.reject(notifyError);
|
|
887
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
888
935
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
889
936
|
}
|
|
890
937
|
|
|
@@ -908,7 +955,7 @@ export default class ReactNativeBleTransport {
|
|
|
908
955
|
return;
|
|
909
956
|
}
|
|
910
957
|
if (protocol === 'V2') {
|
|
911
|
-
this.handleProtocolV2Notification(uuid, new Uint8Array(data));
|
|
958
|
+
this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
|
|
912
959
|
return;
|
|
913
960
|
}
|
|
914
961
|
// console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
|
|
@@ -929,13 +976,18 @@ export default class ReactNativeBleTransport {
|
|
|
929
976
|
// );
|
|
930
977
|
bufferLength = 0;
|
|
931
978
|
buffer = [];
|
|
932
|
-
this.
|
|
979
|
+
if (this.runPromiseDeviceId === uuid) {
|
|
980
|
+
this.runPromise?.resolve(value.toString('hex'));
|
|
981
|
+
}
|
|
933
982
|
}
|
|
934
983
|
} catch (error) {
|
|
935
984
|
Log?.debug('monitor data error: ', error);
|
|
936
985
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
937
|
-
this.
|
|
938
|
-
|
|
986
|
+
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
987
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
988
|
+
} else if (this.runPromiseDeviceId === uuid) {
|
|
989
|
+
this.runPromise?.reject(notifyError);
|
|
990
|
+
}
|
|
939
991
|
}
|
|
940
992
|
}, notifyTransactionId);
|
|
941
993
|
|
|
@@ -943,13 +995,18 @@ export default class ReactNativeBleTransport {
|
|
|
943
995
|
}
|
|
944
996
|
|
|
945
997
|
async release(uuid: string, onclose = false) {
|
|
998
|
+
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
999
|
+
return this.releaseNative(uuid, onclose);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private async releaseNative(uuid: string, onclose = false) {
|
|
946
1003
|
const transport = transportCache[uuid];
|
|
947
|
-
if (this.runPromise) {
|
|
1004
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
948
1005
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
949
1006
|
this.runPromise.reject(error);
|
|
950
1007
|
this.runPromise = null;
|
|
951
|
-
this.
|
|
952
|
-
this.
|
|
1008
|
+
this.runPromiseDeviceId = null;
|
|
1009
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
953
1010
|
} else {
|
|
954
1011
|
this.resetProtocolV2Frames(uuid);
|
|
955
1012
|
}
|
|
@@ -957,9 +1014,6 @@ export default class ReactNativeBleTransport {
|
|
|
957
1014
|
if (Platform.OS === 'android' && !onclose && transport) {
|
|
958
1015
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
959
1016
|
this.resetProtocolV2Frames(uuid);
|
|
960
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
961
|
-
this.activeProtocolV2Call = null;
|
|
962
|
-
}
|
|
963
1017
|
return Promise.resolve(true);
|
|
964
1018
|
}
|
|
965
1019
|
|
|
@@ -993,7 +1047,7 @@ export default class ReactNativeBleTransport {
|
|
|
993
1047
|
}
|
|
994
1048
|
|
|
995
1049
|
this.deviceProtocol.delete(uuid);
|
|
996
|
-
|
|
1050
|
+
// Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
|
|
997
1051
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
998
1052
|
this.protocolV2Assemblers.delete(uuid);
|
|
999
1053
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1025,13 +1079,6 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1079
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1026
1080
|
}
|
|
1027
1081
|
|
|
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
1082
|
const protocol = this.getProtocolType(uuid);
|
|
1036
1083
|
if (!protocol) {
|
|
1037
1084
|
throw ERRORS.TypedError(
|
|
@@ -1039,31 +1086,17 @@ export default class ReactNativeBleTransport {
|
|
|
1039
1086
|
`Device protocol has not been detected for ${uuid}`
|
|
1040
1087
|
);
|
|
1041
1088
|
}
|
|
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
|
-
}
|
|
1089
|
+
Log?.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
1062
1090
|
|
|
1063
1091
|
if (protocol === 'V2') {
|
|
1064
1092
|
return this.callProtocolV2(uuid, name, data, options);
|
|
1065
1093
|
}
|
|
1066
1094
|
|
|
1095
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
1096
|
+
if (this.runPromise && !forceRun) {
|
|
1097
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1067
1100
|
return this.callProtocolV1(uuid, name, data, options);
|
|
1068
1101
|
}
|
|
1069
1102
|
|
|
@@ -1080,7 +1113,24 @@ export default class ReactNativeBleTransport {
|
|
|
1080
1113
|
const transport = this.getCachedTransport(uuid);
|
|
1081
1114
|
const runPromise = createDeferred<string>();
|
|
1082
1115
|
runPromise.promise.catch(() => undefined);
|
|
1116
|
+
const supersededRunPromise = this.runPromise;
|
|
1117
|
+
if (supersededRunPromise) {
|
|
1118
|
+
// Only forceRun calls (Initialize/Cancel) reach here with a pending call. Settle
|
|
1119
|
+
// the superseded deferred now so its response race resolves and its finally block
|
|
1120
|
+
// clears its timeout timer; an orphaned timer would otherwise fire much later and
|
|
1121
|
+
// tear down the shared connection while another call is using it.
|
|
1122
|
+
supersededRunPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
1123
|
+
}
|
|
1083
1124
|
this.runPromise = runPromise;
|
|
1125
|
+
this.runPromiseDeviceId = uuid;
|
|
1126
|
+
// A superseded call's late write failure must not clear the successor's ownership;
|
|
1127
|
+
// only the call that still owns the slot may release it.
|
|
1128
|
+
const releaseOwnershipIfCurrent = () => {
|
|
1129
|
+
if (this.runPromise === runPromise) {
|
|
1130
|
+
this.runPromise = null;
|
|
1131
|
+
this.runPromiseDeviceId = null;
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1084
1134
|
const messages = this._messages;
|
|
1085
1135
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1086
1136
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1152,19 +1202,18 @@ export default class ReactNativeBleTransport {
|
|
|
1152
1202
|
buffers,
|
|
1153
1203
|
data => transport.writeWithRetry(data),
|
|
1154
1204
|
e => {
|
|
1155
|
-
|
|
1205
|
+
releaseOwnershipIfCurrent();
|
|
1156
1206
|
Log?.error('writeCharacteristic write error: ', e);
|
|
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 => {
|
|
@@ -1181,36 +1230,19 @@ export default class ReactNativeBleTransport {
|
|
|
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
|
},
|
|
1212
1244
|
e => {
|
|
1213
|
-
|
|
1245
|
+
releaseOwnershipIfCurrent();
|
|
1214
1246
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1215
1247
|
}
|
|
1216
1248
|
);
|
|
@@ -1218,12 +1250,11 @@ 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
|
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1224
1255
|
} catch (e) {
|
|
1225
1256
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1226
|
-
|
|
1257
|
+
releaseOwnershipIfCurrent();
|
|
1227
1258
|
if (e.errorCode === BleErrorCode.DeviceDisconnected) {
|
|
1228
1259
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
|
|
1229
1260
|
} else if (e.errorCode === BleErrorCode.OperationStartFailed) {
|
|
@@ -1256,20 +1287,33 @@ export default class ReactNativeBleTransport {
|
|
|
1256
1287
|
throw new Error('Returning data is not string.');
|
|
1257
1288
|
}
|
|
1258
1289
|
|
|
1259
|
-
Log?.debug('receive data: ', response);
|
|
1260
1290
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1261
1291
|
return check.call(jsonData);
|
|
1262
1292
|
} catch (e) {
|
|
1263
|
-
if (name === '
|
|
1264
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1293
|
+
if (name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1294
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
|
|
1265
1295
|
} else {
|
|
1266
1296
|
Log?.error('call error: ', e);
|
|
1267
1297
|
}
|
|
1298
|
+
const isProbeTimeout =
|
|
1299
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1300
|
+
// A call that has been superseded (forceRun) or cleaned up no longer owns the
|
|
1301
|
+
// transport; its late timeout must not tear down the connection the current
|
|
1302
|
+
// call is actively using.
|
|
1303
|
+
const isStaleCall = this.runPromise !== runPromise;
|
|
1304
|
+
if (
|
|
1305
|
+
!isProbeTimeout &&
|
|
1306
|
+
!isStaleCall &&
|
|
1307
|
+
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1308
|
+
) {
|
|
1309
|
+
await this.disconnect(uuid);
|
|
1310
|
+
}
|
|
1268
1311
|
throw e;
|
|
1269
1312
|
} finally {
|
|
1270
1313
|
if (timeout) clearTimeout(timeout);
|
|
1271
1314
|
if (this.runPromise === runPromise) {
|
|
1272
1315
|
this.runPromise = null;
|
|
1316
|
+
this.runPromiseDeviceId = null;
|
|
1273
1317
|
}
|
|
1274
1318
|
}
|
|
1275
1319
|
}
|
|
@@ -1279,8 +1323,9 @@ export default class ReactNativeBleTransport {
|
|
|
1279
1323
|
}
|
|
1280
1324
|
|
|
1281
1325
|
async disconnect(session: string) {
|
|
1282
|
-
|
|
1326
|
+
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1283
1327
|
const transport = transportCache[session];
|
|
1328
|
+
const monitorToken = transport?.monitorToken ?? this.monitorTokens.get(session);
|
|
1284
1329
|
|
|
1285
1330
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
1286
1331
|
// from being triggered when we cancel the connection below
|
|
@@ -1341,20 +1386,16 @@ export default class ReactNativeBleTransport {
|
|
|
1341
1386
|
this.deviceProtocolHints.delete(session);
|
|
1342
1387
|
this.protocolV2Assemblers.delete(session);
|
|
1343
1388
|
this.resetProtocolV2Frames(session);
|
|
1344
|
-
if (this.activeProtocolV2Call?.uuid === session) {
|
|
1345
|
-
this.activeProtocolV2Call = null;
|
|
1346
|
-
}
|
|
1347
1389
|
|
|
1348
1390
|
// emit the disconnect event
|
|
1349
1391
|
try {
|
|
1350
|
-
this.
|
|
1351
|
-
name: transport?.device?.name,
|
|
1352
|
-
id: session,
|
|
1353
|
-
connectId: session,
|
|
1354
|
-
});
|
|
1392
|
+
this.emitDeviceDisconnect(session, transport?.device?.name, monitorToken);
|
|
1355
1393
|
} catch (e) {
|
|
1356
1394
|
Log?.error('resetSession: emit disconnect event error: ', e);
|
|
1357
1395
|
}
|
|
1396
|
+
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1397
|
+
this.monitorTokens.delete(session);
|
|
1398
|
+
}
|
|
1358
1399
|
// eslint-disable-next-line no-promise-executor-return
|
|
1359
1400
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
|
1360
1401
|
}
|
|
@@ -1365,6 +1406,7 @@ export default class ReactNativeBleTransport {
|
|
|
1365
1406
|
// this.runPromise.reject(new Error('Transport_CallCanceled'));
|
|
1366
1407
|
}
|
|
1367
1408
|
this.runPromise = null;
|
|
1409
|
+
this.runPromiseDeviceId = null;
|
|
1368
1410
|
}
|
|
1369
1411
|
|
|
1370
1412
|
private getCachedTransport(uuid: string) {
|
|
@@ -1385,7 +1427,7 @@ export default class ReactNativeBleTransport {
|
|
|
1385
1427
|
private createProtocolDetectionError() {
|
|
1386
1428
|
return ERRORS.TypedError(
|
|
1387
1429
|
HardwareErrorCode.BleTimeoutError,
|
|
1388
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1430
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
1389
1431
|
);
|
|
1390
1432
|
}
|
|
1391
1433
|
|
|
@@ -1398,42 +1440,61 @@ export default class ReactNativeBleTransport {
|
|
|
1398
1440
|
private async detectProtocol(
|
|
1399
1441
|
uuid: string,
|
|
1400
1442
|
expectedProtocol?: ProtocolType,
|
|
1401
|
-
protocolHint?: ProtocolType
|
|
1443
|
+
protocolHint?: ProtocolType,
|
|
1444
|
+
rebuildTransport?: () => Promise<void>
|
|
1402
1445
|
): Promise<ProtocolType> {
|
|
1403
1446
|
if (expectedProtocol === 'V1') {
|
|
1404
1447
|
if (await this.probeProtocolV1(uuid)) {
|
|
1405
1448
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1406
|
-
Log?.debug(
|
|
1449
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1450
|
+
deviceId: uuid,
|
|
1451
|
+
protocol: 'V1',
|
|
1452
|
+
source: 'expected',
|
|
1453
|
+
});
|
|
1407
1454
|
return 'V1';
|
|
1408
1455
|
}
|
|
1409
1456
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1410
1457
|
}
|
|
1411
1458
|
|
|
1412
1459
|
if (expectedProtocol === 'V2') {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1460
|
+
if (await this.probeProtocolV2(uuid)) {
|
|
1461
|
+
this.deviceProtocol.set(uuid, 'V2');
|
|
1462
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1463
|
+
deviceId: uuid,
|
|
1464
|
+
protocol: 'V2',
|
|
1465
|
+
source: 'expected',
|
|
1466
|
+
});
|
|
1467
|
+
return 'V2';
|
|
1468
|
+
}
|
|
1469
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1418
1470
|
}
|
|
1419
1471
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 不能作为最终结论。
|
|
1472
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
1473
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
1423
1474
|
const probeOrder: ProtocolType[] =
|
|
1424
1475
|
protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
1425
1476
|
|
|
1426
1477
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
1427
1478
|
const protocol = probeOrder[i];
|
|
1428
1479
|
if (i > 0) {
|
|
1429
|
-
//
|
|
1480
|
+
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1430
1481
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1482
|
+
if (!transportCache[uuid]) {
|
|
1483
|
+
if (!rebuildTransport) {
|
|
1484
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1485
|
+
}
|
|
1486
|
+
await rebuildTransport();
|
|
1487
|
+
}
|
|
1431
1488
|
}
|
|
1432
1489
|
const detected =
|
|
1433
1490
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
1434
1491
|
if (detected) {
|
|
1435
1492
|
this.deviceProtocol.set(uuid, protocol);
|
|
1436
|
-
Log?.debug(
|
|
1493
|
+
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
1494
|
+
deviceId: uuid,
|
|
1495
|
+
protocol,
|
|
1496
|
+
source: 'probe',
|
|
1497
|
+
});
|
|
1437
1498
|
return protocol;
|
|
1438
1499
|
}
|
|
1439
1500
|
}
|
|
@@ -1444,11 +1505,12 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1505
|
|
|
1445
1506
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
1446
1507
|
const transport = transportCache[uuid];
|
|
1508
|
+
await this.protocolV2Links.invalidateLink(
|
|
1509
|
+
uuid,
|
|
1510
|
+
`Reset notify state after Protocol ${protocol} probe`
|
|
1511
|
+
);
|
|
1447
1512
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1448
1513
|
this.resetProtocolV2Frames(uuid);
|
|
1449
|
-
if (this.activeProtocolV2Call?.uuid === uuid) {
|
|
1450
|
-
this.activeProtocolV2Call = null;
|
|
1451
|
-
}
|
|
1452
1514
|
if (this.runPromise) {
|
|
1453
1515
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1454
1516
|
this.runPromise.reject(error);
|
|
@@ -1500,11 +1562,13 @@ export default class ReactNativeBleTransport {
|
|
|
1500
1562
|
|
|
1501
1563
|
try {
|
|
1502
1564
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1503
|
-
|
|
1565
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1566
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
1567
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1504
1568
|
return true;
|
|
1505
1569
|
} catch (error) {
|
|
1506
1570
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1507
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1571
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1508
1572
|
return false;
|
|
1509
1573
|
}
|
|
1510
1574
|
}
|
|
@@ -1533,13 +1597,9 @@ export default class ReactNativeBleTransport {
|
|
|
1533
1597
|
return detected;
|
|
1534
1598
|
}
|
|
1535
1599
|
|
|
1536
|
-
private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
|
|
1600
|
+
private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
|
|
1537
1601
|
try {
|
|
1538
|
-
if (
|
|
1539
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1540
|
-
this.resetProtocolV2Frames(uuid);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1602
|
+
if (this.monitorTokens.get(uuid) !== monitorToken) return;
|
|
1543
1603
|
|
|
1544
1604
|
if (data.length === 0) return;
|
|
1545
1605
|
|
|
@@ -1552,8 +1612,15 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1612
|
} catch (error) {
|
|
1553
1613
|
Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
|
|
1554
1614
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1615
|
+
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1616
|
+
this.protocolV2Links
|
|
1617
|
+
.invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
|
|
1618
|
+
.catch(invalidateError =>
|
|
1619
|
+
Log?.debug(
|
|
1620
|
+
'[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
|
|
1621
|
+
invalidateError
|
|
1622
|
+
)
|
|
1623
|
+
);
|
|
1557
1624
|
}
|
|
1558
1625
|
}
|
|
1559
1626
|
|
|
@@ -1576,21 +1643,17 @@ export default class ReactNativeBleTransport {
|
|
|
1576
1643
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1577
1644
|
}
|
|
1578
1645
|
|
|
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
1646
|
private resetProtocolV2Frames(uuid: string) {
|
|
1588
|
-
this.
|
|
1589
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1647
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1590
1648
|
}
|
|
1591
1649
|
|
|
1592
|
-
private
|
|
1593
|
-
|
|
1650
|
+
private rejectProtocolV2Frames(uuid: string, error: Error) {
|
|
1651
|
+
this.protocolV2FrameQueues.delete(uuid);
|
|
1652
|
+
const framePromise = this.protocolV2FramePromises.get(uuid);
|
|
1653
|
+
if (framePromise) {
|
|
1654
|
+
this.protocolV2FramePromises.delete(uuid);
|
|
1655
|
+
framePromise.reject(error);
|
|
1656
|
+
}
|
|
1594
1657
|
}
|
|
1595
1658
|
|
|
1596
1659
|
private async readProtocolV2Frame(uuid: string) {
|
|
@@ -1610,10 +1673,46 @@ export default class ReactNativeBleTransport {
|
|
|
1610
1673
|
}
|
|
1611
1674
|
}
|
|
1612
1675
|
|
|
1676
|
+
private async writeProtocolV2Packet(
|
|
1677
|
+
transport: BleTransport,
|
|
1678
|
+
base64: string,
|
|
1679
|
+
context: ProtocolV2CallContext,
|
|
1680
|
+
assertCurrentGeneration: () => void
|
|
1681
|
+
) {
|
|
1682
|
+
let attempt = 0;
|
|
1683
|
+
for (;;) {
|
|
1684
|
+
assertCurrentGeneration();
|
|
1685
|
+
if (context.signal.aborted) {
|
|
1686
|
+
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1687
|
+
}
|
|
1688
|
+
try {
|
|
1689
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1690
|
+
assertCurrentGeneration();
|
|
1691
|
+
return;
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
if (
|
|
1694
|
+
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
1695
|
+
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|
|
1696
|
+
) {
|
|
1697
|
+
throw error;
|
|
1698
|
+
}
|
|
1699
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1700
|
+
attempt += 1;
|
|
1701
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 congested write retry:', {
|
|
1702
|
+
name: context.messageName,
|
|
1703
|
+
attempt,
|
|
1704
|
+
delayMs,
|
|
1705
|
+
});
|
|
1706
|
+
await delay(delayMs);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1613
1711
|
private async writeProtocolV2Frame(
|
|
1614
1712
|
transport: BleTransport,
|
|
1615
1713
|
frame: Uint8Array,
|
|
1616
|
-
|
|
1714
|
+
context: ProtocolV2CallContext,
|
|
1715
|
+
assertCurrentGeneration: () => void
|
|
1617
1716
|
) {
|
|
1618
1717
|
const tuning = getProtocolV2BleTuning();
|
|
1619
1718
|
const packetCapacity = resolveProtocolV2PacketCapacity({
|
|
@@ -1622,37 +1721,24 @@ export default class ReactNativeBleTransport {
|
|
|
1622
1721
|
androidPacketLength: tuning.androidPacketLength,
|
|
1623
1722
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1624
1723
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
shouldThrottle &&
|
|
1646
|
-
packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
|
|
1647
|
-
offset + packetCapacity < frame.length
|
|
1648
|
-
) {
|
|
1649
|
-
await delay(tuning.highVolumeWritePauseMs);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
if (shouldThrottle) {
|
|
1654
|
-
await delay(tuning.highVolumeWriteFlushDelayMs);
|
|
1655
|
-
}
|
|
1724
|
+
await writeProtocolV2BleFrame({
|
|
1725
|
+
frame,
|
|
1726
|
+
packetCapacity,
|
|
1727
|
+
assertActive: assertCurrentGeneration,
|
|
1728
|
+
signal: context.signal,
|
|
1729
|
+
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1730
|
+
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1731
|
+
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1732
|
+
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
1733
|
+
wait: delay,
|
|
1734
|
+
writePacket: packet =>
|
|
1735
|
+
this.writeProtocolV2Packet(
|
|
1736
|
+
transport,
|
|
1737
|
+
Buffer.from(packet).toString('base64'),
|
|
1738
|
+
context,
|
|
1739
|
+
assertCurrentGeneration
|
|
1740
|
+
),
|
|
1741
|
+
});
|
|
1656
1742
|
}
|
|
1657
1743
|
|
|
1658
1744
|
private async callProtocolV2(
|
|
@@ -1665,101 +1751,76 @@ export default class ReactNativeBleTransport {
|
|
|
1665
1751
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
1666
1752
|
}
|
|
1667
1753
|
|
|
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
|
-
};
|
|
1754
|
+
const callOptions = options;
|
|
1693
1755
|
const highVolumeWrite = LogBlockCommand.has(name);
|
|
1694
1756
|
|
|
1695
1757
|
if (highVolumeWrite) {
|
|
1696
1758
|
const tuning = getProtocolV2BleTuning();
|
|
1697
|
-
Log?.debug(
|
|
1698
|
-
'[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
|
|
1759
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
|
|
1699
1760
|
name,
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
burstSize: tuning.highVolumeWriteBurstSize,
|
|
1704
|
-
pauseMs: tuning.highVolumeWritePauseMs,
|
|
1705
|
-
flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
|
|
1706
|
-
writeWithResponse: tuning.highVolumeWriteWithResponse,
|
|
1707
|
-
}
|
|
1708
|
-
);
|
|
1761
|
+
writeMode: 'withoutResponse',
|
|
1762
|
+
packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
|
|
1763
|
+
});
|
|
1709
1764
|
}
|
|
1710
1765
|
|
|
1711
1766
|
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;
|
|
1767
|
+
return await this.protocolV2Links.call(
|
|
1768
|
+
uuid,
|
|
1769
|
+
() => this.createProtocolV2Adapter(uuid),
|
|
1770
|
+
name,
|
|
1771
|
+
data,
|
|
1772
|
+
callOptions
|
|
1773
|
+
);
|
|
1742
1774
|
} catch (e) {
|
|
1743
|
-
if (this.isActiveProtocolV2Call(uuid, callToken)) {
|
|
1744
|
-
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1745
|
-
this.resetProtocolV2Frames(uuid);
|
|
1746
|
-
}
|
|
1747
1775
|
Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
|
|
1748
1776
|
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
1777
|
}
|
|
1761
1778
|
}
|
|
1762
1779
|
|
|
1780
|
+
private createProtocolV2Adapter(uuid: string) {
|
|
1781
|
+
const generation = this.monitorTokens.get(uuid) ?? 0;
|
|
1782
|
+
const assertCurrentGeneration = () => {
|
|
1783
|
+
if (this.monitorTokens.get(uuid) !== generation) {
|
|
1784
|
+
throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
return {
|
|
1789
|
+
router: PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
1790
|
+
maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
|
|
1791
|
+
generation,
|
|
1792
|
+
prepareCall: () => {
|
|
1793
|
+
assertCurrentGeneration();
|
|
1794
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1795
|
+
this.resetProtocolV2Frames(uuid);
|
|
1796
|
+
},
|
|
1797
|
+
writeFrame: async (frame: Uint8Array, context: ProtocolV2CallContext) => {
|
|
1798
|
+
assertCurrentGeneration();
|
|
1799
|
+
const currentTransport = this.getCachedTransport(uuid);
|
|
1800
|
+
await this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
|
|
1801
|
+
},
|
|
1802
|
+
readFrame: async () => {
|
|
1803
|
+
assertCurrentGeneration();
|
|
1804
|
+
const rxFrame = await this.readProtocolV2Frame(uuid);
|
|
1805
|
+
if (!(rxFrame instanceof Uint8Array)) {
|
|
1806
|
+
throw new Error('Protocol V2 response is not Uint8Array');
|
|
1807
|
+
}
|
|
1808
|
+
return rxFrame;
|
|
1809
|
+
},
|
|
1810
|
+
reset: (reason: string) => {
|
|
1811
|
+
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1812
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1813
|
+
},
|
|
1814
|
+
logger: Log,
|
|
1815
|
+
logPrefix: 'ProtocolV2 RN-BLE',
|
|
1816
|
+
createTimeoutError: (messageName: string, timeout: number) =>
|
|
1817
|
+
ERRORS.TypedError(
|
|
1818
|
+
HardwareErrorCode.BleTimeoutError,
|
|
1819
|
+
`BLE response timeout after ${timeout}ms for ${messageName}`
|
|
1820
|
+
),
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1763
1824
|
getProtocolType(path: string): ProtocolType | undefined {
|
|
1764
1825
|
return this.deviceProtocol.get(path);
|
|
1765
1826
|
}
|