@onekeyfe/hd-transport-react-native 1.1.27-alpha.5 → 1.1.27-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,24 @@
1
1
  import * as transport from '@onekeyfe/hd-transport';
2
2
  import transport__default, { OneKeyDeviceInfoBase } from '@onekeyfe/hd-transport';
3
- import { Device, BleManager, Characteristic, Subscription } from 'react-native-ble-plx';
3
+ import { Device, Characteristic, Subscription, BleManager } from 'react-native-ble-plx';
4
4
  import { Deferred } from '@onekeyfe/hd-shared';
5
5
  import EventEmitter from 'events';
6
6
 
7
+ declare class BleTransport {
8
+ id: string;
9
+ name: string;
10
+ device: Device;
11
+ mtuSize: number;
12
+ writeCharacteristic: Characteristic;
13
+ notifyCharacteristic: Characteristic;
14
+ notifySubscription?: Subscription;
15
+ disconnectSubscription?: Subscription;
16
+ static MAX_RETRIES: number;
17
+ static RETRY_DELAY: number;
18
+ constructor(device: Device, writeCharacteristic: Characteristic, notifyCharacteristic: Characteristic);
19
+ writeWithRetry(data: string, retryCount?: number): Promise<void>;
20
+ }
21
+
7
22
  type TransportOptions = {
8
23
  scanTimeout?: number;
9
24
  };
@@ -12,6 +27,10 @@ type BleAcquireInput = {
12
27
  forceCleanRunPromise?: boolean;
13
28
  };
14
29
 
30
+ type ResolvedBleCharacteristics = {
31
+ writeCharacteristic: Characteristic;
32
+ notifyCharacteristic: Characteristic;
33
+ };
15
34
  type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
16
35
  declare class ReactNativeBleTransport {
17
36
  blePlxManager: BleManager | undefined;
@@ -22,11 +41,15 @@ declare class ReactNativeBleTransport {
22
41
  scanTimeout: number;
23
42
  runPromise: Deferred<any> | null;
24
43
  emitter?: EventEmitter;
44
+ firmwareUploadWriteRecoveryIds: Set<string>;
25
45
  constructor(options: TransportOptions);
26
46
  init(_logger: any, emitter: EventEmitter): void;
27
47
  configure(signedData: any): void;
28
48
  listen(): void;
29
49
  getPlxManager(): Promise<BleManager>;
50
+ resolveCharacteristics(device: Device): Promise<ResolvedBleCharacteristics>;
51
+ attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string): void;
52
+ reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport): Promise<void>;
30
53
  enumerate(): Promise<IOneKeyDevice[]>;
31
54
  acquire(input: BleAcquireInput): Promise<{
32
55
  uuid: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAIL,UAAU,IAAI,aAAa,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAGhB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAgBhC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAcjE,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,MAAM,CAAC;AA0C1D,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC1C,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IAEzC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,IAAI,SAA6B;IAEjC,UAAU,UAAS;IAEnB,OAAO,UAAS;IAEhB,WAAW,SAAQ;IAEnB,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAExC,OAAO,CAAC,EAAE,YAAY,CAAC;gBAEX,OAAO,EAAE,gBAAgB;IAIrC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY;IAIxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,MAAM;IAIN,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC;IAWjC,SAAS;IA6FT,OAAO,CAAC,KAAK,EAAE,eAAe;;;IAiNpC,sBAAsB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY;IA+E5E,OAAO,CAAC,IAAI,EAAE,MAAM;IA4BpB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA2HpE,IAAI;IAIE,UAAU,CAAC,OAAO,EAAE,MAAM;IA0EhC,MAAM;CAOP"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAIL,UAAU,IAAI,aAAa,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAGhB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAahC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAG1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAkBjE,KAAK,0BAA0B,GAAG;IAChC,mBAAmB,EAAE,cAAc,CAAC;IACpC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AA+CF,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,MAAM,CAAC;AA0C1D,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC1C,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IAEzC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,IAAI,SAA6B;IAEjC,UAAU,UAAS;IAEnB,OAAO,UAAS;IAEhB,WAAW,SAAQ;IAEnB,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IAExC,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,8BAA8B,cAAqB;gBAEvC,OAAO,EAAE,gBAAgB;IAIrC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY;IAIxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,MAAM;IAIN,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC;IAMjC,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAuEjF,4BAA4B,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IA0B5E,gCAAgC,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY;IA6CtE,SAAS;IA6FT,OAAO,CAAC,KAAK,EAAE,eAAe;;;IAmIpC,sBAAsB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY;IAmF5E,OAAO,CAAC,IAAI,EAAE,MAAM;IA4BpB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA6MpE,IAAI;IAIE,UAAU,CAAC,OAAO,EAAE,MAAM;IA0EhC,MAAM;CAOP"}
package/dist/index.js CHANGED
@@ -214,6 +214,35 @@ BleTransport.RETRY_DELAY = 2000;
214
214
  const { check, buildBuffers, receiveOne, parseConfigure } = transport__default["default"];
215
215
  const Log = hdCore.getLogger(hdCore.LoggerNames.HdBleTransport);
216
216
  const transportCache = {};
217
+ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
218
+ const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
219
+ const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
220
+ const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
221
+ const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
222
+ const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
223
+ const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
224
+ const ANDROID_GATT_CONGESTED_STATUS = 143;
225
+ const delay = (ms) => new Promise(resolve => {
226
+ setTimeout(resolve, ms);
227
+ });
228
+ const getFirmwareUploadWriteRetryType = (error) => {
229
+ if (!error || typeof error !== 'object')
230
+ return null;
231
+ const bleWriteError = error;
232
+ if (bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected ||
233
+ bleWriteError.errorCode === reactNativeBlePlx.BleErrorCode.CharacteristicNotFound) {
234
+ return 'reconnectable';
235
+ }
236
+ if (bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
237
+ bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS) {
238
+ return 'congested';
239
+ }
240
+ const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
241
+ .filter(value => typeof value === 'string')
242
+ .join(' ');
243
+ return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
244
+ };
245
+ const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
217
246
  let connectOptions = {
218
247
  requestMTU: 256,
219
248
  timeout: 3000,
@@ -254,6 +283,7 @@ class ReactNativeBleTransport {
254
283
  this.stopped = false;
255
284
  this.scanTimeout = 3000;
256
285
  this.runPromise = null;
286
+ this.firmwareUploadWriteRecoveryIds = new Set();
257
287
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : 3000;
258
288
  }
259
289
  init(_logger, emitter) {
@@ -272,6 +302,134 @@ class ReactNativeBleTransport {
272
302
  this.blePlxManager = new reactNativeBlePlx.BleManager();
273
303
  return Promise.resolve(this.blePlxManager);
274
304
  }
305
+ resolveCharacteristics(device) {
306
+ return __awaiter(this, void 0, void 0, function* () {
307
+ yield device.discoverAllServicesAndCharacteristics();
308
+ let infos = tryToGetConfiguration(device);
309
+ let characteristics;
310
+ if (!infos) {
311
+ for (const serviceUuid of getBluetoothServiceUuids()) {
312
+ try {
313
+ characteristics = yield device.characteristicsForService(serviceUuid);
314
+ infos = getInfosForServiceUuid(serviceUuid, 'classic');
315
+ break;
316
+ }
317
+ catch (e) {
318
+ Log === null || Log === void 0 ? void 0 : Log.error(e);
319
+ }
320
+ }
321
+ }
322
+ if (!infos) {
323
+ try {
324
+ Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
325
+ yield device.cancelConnection();
326
+ }
327
+ catch (e) {
328
+ Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection error when service not found: ', e.message || e.reason);
329
+ }
330
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
331
+ }
332
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
333
+ if (!characteristics) {
334
+ characteristics = yield device.characteristicsForService(serviceUuid);
335
+ }
336
+ if (!characteristics) {
337
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound);
338
+ }
339
+ let writeCharacteristic;
340
+ let notifyCharacteristic;
341
+ for (const c of characteristics) {
342
+ if (c.uuid === writeUuid) {
343
+ writeCharacteristic = c;
344
+ }
345
+ else if (c.uuid === notifyUuid) {
346
+ notifyCharacteristic = c;
347
+ }
348
+ }
349
+ if (!writeCharacteristic) {
350
+ throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
351
+ }
352
+ if (!notifyCharacteristic) {
353
+ throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
354
+ }
355
+ if (!writeCharacteristic.isWritableWithResponse) {
356
+ throw hdShared.ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
357
+ }
358
+ if (!notifyCharacteristic.isNotifiable) {
359
+ throw hdShared.ERRORS.TypedError('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
360
+ }
361
+ return {
362
+ writeCharacteristic,
363
+ notifyCharacteristic,
364
+ };
365
+ });
366
+ }
367
+ attachDisconnectSubscription(transport, device, uuid) {
368
+ var _a;
369
+ (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
370
+ transport.disconnectSubscription = device.onDisconnected(() => {
371
+ var _a;
372
+ if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
373
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
374
+ return;
375
+ }
376
+ try {
377
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
378
+ (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
379
+ name: device === null || device === void 0 ? void 0 : device.name,
380
+ id: device === null || device === void 0 ? void 0 : device.id,
381
+ connectId: device === null || device === void 0 ? void 0 : device.id,
382
+ });
383
+ if (this.runPromise) {
384
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError));
385
+ }
386
+ }
387
+ catch (e) {
388
+ Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect error: ', e);
389
+ }
390
+ finally {
391
+ this.release(uuid);
392
+ }
393
+ });
394
+ }
395
+ reconnectFirmwareUploadTransport(uuid, transport) {
396
+ var _a, _b;
397
+ return __awaiter(this, void 0, void 0, function* () {
398
+ this.firmwareUploadWriteRecoveryIds.add(uuid);
399
+ try {
400
+ (_a = transport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
401
+ transport.disconnectSubscription = undefined;
402
+ (_b = transport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
403
+ transport.notifySubscription = undefined;
404
+ let { device } = transport;
405
+ const isConnected = yield device.isConnected().catch(() => false);
406
+ if (!isConnected) {
407
+ try {
408
+ device = yield device.connect(connectOptions);
409
+ }
410
+ catch (e) {
411
+ if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
412
+ e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
413
+ connectOptions = {};
414
+ device = yield device.connect();
415
+ }
416
+ else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
417
+ throw e;
418
+ }
419
+ }
420
+ }
421
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
422
+ transport.device = device;
423
+ transport.writeCharacteristic = writeCharacteristic;
424
+ transport.notifyCharacteristic = notifyCharacteristic;
425
+ transport.notifySubscription = this._monitorCharacteristic(notifyCharacteristic, uuid);
426
+ this.attachDisconnectSubscription(transport, device, uuid);
427
+ }
428
+ finally {
429
+ this.firmwareUploadWriteRecoveryIds.delete(uuid);
430
+ }
431
+ });
432
+ }
275
433
  enumerate() {
276
434
  return __awaiter(this, void 0, void 0, function* () {
277
435
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
@@ -441,60 +599,7 @@ class ReactNativeBleTransport {
441
599
  }
442
600
  }
443
601
  }
444
- yield device.discoverAllServicesAndCharacteristics();
445
- let infos = tryToGetConfiguration(device);
446
- let characteristics;
447
- if (!infos) {
448
- for (const serviceUuid of getBluetoothServiceUuids()) {
449
- try {
450
- characteristics = yield device.characteristicsForService(serviceUuid);
451
- infos = getInfosForServiceUuid(serviceUuid, 'classic');
452
- break;
453
- }
454
- catch (e) {
455
- Log === null || Log === void 0 ? void 0 : Log.error(e);
456
- }
457
- }
458
- }
459
- if (!infos) {
460
- try {
461
- Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection when service not found');
462
- yield device.cancelConnection();
463
- }
464
- catch (e) {
465
- Log === null || Log === void 0 ? void 0 : Log.debug('cancel connection error when service not found: ', e.message || e.reason);
466
- }
467
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleServiceNotFound);
468
- }
469
- const { serviceUuid, writeUuid, notifyUuid } = infos;
470
- if (!characteristics) {
471
- characteristics = yield device.characteristicsForService(serviceUuid);
472
- }
473
- if (!characteristics) {
474
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotFound);
475
- }
476
- let writeCharacteristic;
477
- let notifyCharacteristic;
478
- for (const c of characteristics) {
479
- if (c.uuid === writeUuid) {
480
- writeCharacteristic = c;
481
- }
482
- else if (c.uuid === notifyUuid) {
483
- notifyCharacteristic = c;
484
- }
485
- }
486
- if (!writeCharacteristic) {
487
- throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
488
- }
489
- if (!notifyCharacteristic) {
490
- throw hdShared.ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
491
- }
492
- if (!writeCharacteristic.isWritableWithResponse) {
493
- throw hdShared.ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
494
- }
495
- if (!notifyCharacteristic.isNotifiable) {
496
- throw hdShared.ERRORS.TypedError('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
497
- }
602
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
498
603
  yield this.release(uuid);
499
604
  const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
500
605
  transport.notifySubscription = this._monitorCharacteristic(transport.notifyCharacteristic, uuid);
@@ -504,26 +609,7 @@ class ReactNativeBleTransport {
504
609
  id: device.id,
505
610
  connectId: device.id,
506
611
  });
507
- transport.disconnectSubscription = device.onDisconnected(() => {
508
- var _a;
509
- try {
510
- Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect: ', device === null || device === void 0 ? void 0 : device.id);
511
- (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
512
- name: device === null || device === void 0 ? void 0 : device.name,
513
- id: device === null || device === void 0 ? void 0 : device.id,
514
- connectId: device === null || device === void 0 ? void 0 : device.id,
515
- });
516
- if (this.runPromise) {
517
- this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError));
518
- }
519
- }
520
- catch (e) {
521
- Log === null || Log === void 0 ? void 0 : Log.debug('device disconnect error: ', e);
522
- }
523
- finally {
524
- this.release(uuid);
525
- }
526
- });
612
+ this.attachDisconnectSubscription(transport, device, uuid);
527
613
  return { uuid };
528
614
  });
529
615
  }
@@ -534,6 +620,10 @@ class ReactNativeBleTransport {
534
620
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
535
621
  if (error) {
536
622
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
623
+ if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
624
+ Log === null || Log === void 0 ? void 0 : Log.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
625
+ return;
626
+ }
537
627
  if (this.runPromise) {
538
628
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
539
629
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
@@ -659,6 +749,36 @@ class ReactNativeBleTransport {
659
749
  }
660
750
  });
661
751
  }
752
+ function writeFirmwareUploadChunkedData(buffers, writeFunction, onError) {
753
+ return __awaiter(this, void 0, void 0, function* () {
754
+ let index = 0;
755
+ let packetsWritten = 0;
756
+ let chunk = ByteBuffer__default["default"].allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
757
+ while (index < buffers.length) {
758
+ const buffer = buffers[index].toBuffer();
759
+ chunk.append(buffer);
760
+ index += 1;
761
+ if (chunk.offset === FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY || index >= buffers.length) {
762
+ chunk.reset();
763
+ try {
764
+ yield writeFunction(chunk.toString('base64'));
765
+ packetsWritten += 1;
766
+ chunk = ByteBuffer__default["default"].allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
767
+ if (packetsWritten % FIRMWARE_UPLOAD_WRITE_BURST_SIZE === 0 && index < buffers.length) {
768
+ yield delay(FIRMWARE_UPLOAD_WRITE_PAUSE_MS);
769
+ }
770
+ }
771
+ catch (e) {
772
+ onError(e);
773
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
774
+ }
775
+ }
776
+ }
777
+ if (packetsWritten > 0) {
778
+ yield delay(FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS);
779
+ }
780
+ });
781
+ }
662
782
  if (name === 'EmmcFileWrite') {
663
783
  yield writeChunkedData(buffers, data => transport$1.writeWithRetry(data), e => {
664
784
  this.runPromise = null;
@@ -666,8 +786,53 @@ class ReactNativeBleTransport {
666
786
  });
667
787
  }
668
788
  else if (name === 'FirmwareUpload') {
669
- yield writeChunkedData(buffers, (data) => __awaiter(this, void 0, void 0, function* () {
670
- yield transport$1.writeCharacteristic.writeWithoutResponse(data);
789
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
790
+ packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
791
+ burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
792
+ pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
793
+ flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
794
+ maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
795
+ });
796
+ yield writeFirmwareUploadChunkedData(buffers, (data) => __awaiter(this, void 0, void 0, function* () {
797
+ let attempt = 0;
798
+ while (true) {
799
+ try {
800
+ yield transport$1.writeCharacteristic.writeWithoutResponse(data);
801
+ return;
802
+ }
803
+ catch (error) {
804
+ const retryType = getFirmwareUploadWriteRetryType(error);
805
+ if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
806
+ throw error;
807
+ }
808
+ const shouldReconnect = retryType === 'reconnectable';
809
+ const delayMs = shouldReconnect
810
+ ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
811
+ : resolveFirmwareUploadRetryDelay(attempt);
812
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
813
+ attempt: attempt + 1,
814
+ delayMs,
815
+ reconnect: shouldReconnect,
816
+ error,
817
+ });
818
+ if (shouldReconnect) {
819
+ this.firmwareUploadWriteRecoveryIds.add(uuid);
820
+ }
821
+ yield delay(delayMs);
822
+ attempt += 1;
823
+ if (shouldReconnect) {
824
+ try {
825
+ yield this.reconnectFirmwareUploadTransport(uuid, transport$1);
826
+ }
827
+ catch (e) {
828
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
829
+ if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
830
+ throw e;
831
+ }
832
+ }
833
+ }
834
+ }
835
+ }
671
836
  }), e => {
672
837
  this.runPromise = null;
673
838
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.1.27-alpha.5",
3
+ "version": "1.1.27-alpha.6",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -19,11 +19,11 @@
19
19
  "lint:fix": "eslint . --fix"
20
20
  },
21
21
  "dependencies": {
22
- "@onekeyfe/hd-core": "1.1.27-alpha.5",
23
- "@onekeyfe/hd-shared": "1.1.27-alpha.5",
24
- "@onekeyfe/hd-transport": "1.1.27-alpha.5",
22
+ "@onekeyfe/hd-core": "1.1.27-alpha.6",
23
+ "@onekeyfe/hd-shared": "1.1.27-alpha.6",
24
+ "@onekeyfe/hd-transport": "1.1.27-alpha.6",
25
25
  "@onekeyfe/react-native-ble-utils": "^0.1.4",
26
26
  "react-native-ble-plx": "3.5.1"
27
27
  },
28
- "gitHead": "0c7353e097121596ad4076a531a473408471674c"
28
+ "gitHead": "a1fb7786313ccee4b1e570c53ae2c05a96f785df"
29
29
  }
package/src/index.ts CHANGED
@@ -38,6 +38,60 @@ const { check, buildBuffers, receiveOne, parseConfigure } = transport;
38
38
  const Log = getLogger(LoggerNames.HdBleTransport);
39
39
 
40
40
  const transportCache: Record<string, BleTransport> = {};
41
+ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
42
+ const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
43
+ const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
44
+ const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
45
+ const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
46
+ const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
47
+ const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
48
+ Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
49
+ const ANDROID_GATT_CONGESTED_STATUS = 143;
50
+
51
+ type FirmwareUploadWriteRetryType = 'congested' | 'reconnectable';
52
+ type ResolvedBleCharacteristics = {
53
+ writeCharacteristic: Characteristic;
54
+ notifyCharacteristic: Characteristic;
55
+ };
56
+
57
+ const delay = (ms: number) =>
58
+ new Promise<void>(resolve => {
59
+ setTimeout(resolve, ms);
60
+ });
61
+
62
+ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRetryType | null => {
63
+ if (!error || typeof error !== 'object') return null;
64
+ const bleWriteError = error as {
65
+ androidErrorCode?: unknown;
66
+ status?: unknown;
67
+ errorCode?: unknown;
68
+ reason?: unknown;
69
+ message?: unknown;
70
+ name?: unknown;
71
+ };
72
+
73
+ if (
74
+ bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
75
+ bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
76
+ ) {
77
+ return 'reconnectable';
78
+ }
79
+
80
+ if (
81
+ bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
82
+ bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
83
+ ) {
84
+ return 'congested';
85
+ }
86
+
87
+ const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
88
+ .filter(value => typeof value === 'string')
89
+ .join(' ');
90
+ return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
91
+ };
92
+
93
+ const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
94
+ Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
41
95
 
42
96
  let connectOptions: Record<string, unknown> = {
43
97
  requestMTU: 256,
@@ -104,6 +158,8 @@ export default class ReactNativeBleTransport {
104
158
 
105
159
  emitter?: EventEmitter;
106
160
 
161
+ firmwareUploadWriteRecoveryIds = new Set<string>();
162
+
107
163
  constructor(options: TransportOptions) {
108
164
  this.scanTimeout = options.scanTimeout ?? 3000;
109
165
  }
@@ -128,6 +184,143 @@ export default class ReactNativeBleTransport {
128
184
  return Promise.resolve(this.blePlxManager);
129
185
  }
130
186
 
187
+ async resolveCharacteristics(device: Device): Promise<ResolvedBleCharacteristics> {
188
+ await device.discoverAllServicesAndCharacteristics();
189
+ let infos = tryToGetConfiguration(device);
190
+ let characteristics: Characteristic[] | undefined;
191
+
192
+ if (!infos) {
193
+ for (const serviceUuid of getBluetoothServiceUuids()) {
194
+ try {
195
+ characteristics = await device.characteristicsForService(serviceUuid);
196
+ infos = getInfosForServiceUuid(serviceUuid, 'classic');
197
+ break;
198
+ } catch (e) {
199
+ Log?.error(e);
200
+ }
201
+ }
202
+ }
203
+
204
+ if (!infos) {
205
+ try {
206
+ Log?.debug('cancel connection when service not found');
207
+ await device.cancelConnection();
208
+ } catch (e) {
209
+ Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
210
+ }
211
+ throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
212
+ }
213
+
214
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
215
+
216
+ if (!characteristics) {
217
+ characteristics = await device.characteristicsForService(serviceUuid);
218
+ }
219
+
220
+ if (!characteristics) {
221
+ throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
222
+ }
223
+
224
+ let writeCharacteristic;
225
+ let notifyCharacteristic;
226
+ for (const c of characteristics) {
227
+ if (c.uuid === writeUuid) {
228
+ writeCharacteristic = c;
229
+ } else if (c.uuid === notifyUuid) {
230
+ notifyCharacteristic = c;
231
+ }
232
+ }
233
+
234
+ if (!writeCharacteristic) {
235
+ throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
236
+ }
237
+
238
+ if (!notifyCharacteristic) {
239
+ throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
240
+ }
241
+
242
+ if (!writeCharacteristic.isWritableWithResponse) {
243
+ throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
244
+ }
245
+
246
+ if (!notifyCharacteristic.isNotifiable) {
247
+ throw ERRORS.TypedError(
248
+ 'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
249
+ );
250
+ }
251
+
252
+ return {
253
+ writeCharacteristic,
254
+ notifyCharacteristic,
255
+ };
256
+ }
257
+
258
+ attachDisconnectSubscription(transport: BleTransport, device: Device, uuid: string) {
259
+ transport.disconnectSubscription?.remove();
260
+ transport.disconnectSubscription = device.onDisconnected(() => {
261
+ if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
262
+ Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
263
+ return;
264
+ }
265
+
266
+ try {
267
+ Log?.debug('device disconnect: ', device?.id);
268
+ this.emitter?.emit('device-disconnect', {
269
+ name: device?.name,
270
+ id: device?.id,
271
+ connectId: device?.id,
272
+ });
273
+ if (this.runPromise) {
274
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
275
+ }
276
+ } catch (e) {
277
+ Log?.debug('device disconnect error: ', e);
278
+ } finally {
279
+ this.release(uuid);
280
+ }
281
+ });
282
+ }
283
+
284
+ async reconnectFirmwareUploadTransport(uuid: string, transport: BleTransport) {
285
+ this.firmwareUploadWriteRecoveryIds.add(uuid);
286
+ try {
287
+ transport.disconnectSubscription?.remove();
288
+ transport.disconnectSubscription = undefined;
289
+ transport.notifySubscription?.remove();
290
+ transport.notifySubscription = undefined;
291
+
292
+ let { device } = transport;
293
+ const isConnected = await device.isConnected().catch(() => false);
294
+ if (!isConnected) {
295
+ try {
296
+ device = await device.connect(connectOptions);
297
+ } catch (e) {
298
+ if (
299
+ e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
300
+ e.errorCode === BleErrorCode.OperationCancelled
301
+ ) {
302
+ connectOptions = {};
303
+ device = await device.connect();
304
+ } else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
305
+ throw e;
306
+ }
307
+ }
308
+ }
309
+
310
+ const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(
311
+ device
312
+ );
313
+
314
+ transport.device = device;
315
+ transport.writeCharacteristic = writeCharacteristic;
316
+ transport.notifyCharacteristic = notifyCharacteristic;
317
+ transport.notifySubscription = this._monitorCharacteristic(notifyCharacteristic, uuid);
318
+ this.attachDisconnectSubscription(transport, device, uuid);
319
+ } finally {
320
+ this.firmwareUploadWriteRecoveryIds.delete(uuid);
321
+ }
322
+ }
323
+
131
324
  /**
132
325
  * 获取设备列表
133
326
  * 在搜索超过超时时间或设备数量大于 5 台时,返回 OneKey 设备,
@@ -334,69 +527,7 @@ export default class ReactNativeBleTransport {
334
527
  }
335
528
  }
336
529
 
337
- await device.discoverAllServicesAndCharacteristics();
338
- let infos = tryToGetConfiguration(device);
339
- let characteristics;
340
-
341
- if (!infos) {
342
- for (const serviceUuid of getBluetoothServiceUuids()) {
343
- try {
344
- characteristics = await device.characteristicsForService(serviceUuid);
345
- infos = getInfosForServiceUuid(serviceUuid, 'classic');
346
- break;
347
- } catch (e) {
348
- Log?.error(e);
349
- }
350
- }
351
- }
352
-
353
- if (!infos) {
354
- try {
355
- Log?.debug('cancel connection when service not found');
356
- await device.cancelConnection();
357
- } catch (e) {
358
- Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
359
- }
360
- throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
361
- }
362
-
363
- const { serviceUuid, writeUuid, notifyUuid } = infos;
364
-
365
- if (!characteristics) {
366
- characteristics = await device.characteristicsForService(serviceUuid);
367
- }
368
-
369
- if (!characteristics) {
370
- throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
371
- }
372
-
373
- let writeCharacteristic;
374
- let notifyCharacteristic;
375
- for (const c of characteristics) {
376
- if (c.uuid === writeUuid) {
377
- writeCharacteristic = c;
378
- } else if (c.uuid === notifyUuid) {
379
- notifyCharacteristic = c;
380
- }
381
- }
382
-
383
- if (!writeCharacteristic) {
384
- throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
385
- }
386
-
387
- if (!notifyCharacteristic) {
388
- throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
389
- }
390
-
391
- if (!writeCharacteristic.isWritableWithResponse) {
392
- throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
393
- }
394
-
395
- if (!notifyCharacteristic.isNotifiable) {
396
- throw ERRORS.TypedError(
397
- 'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
398
- );
399
- }
530
+ const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(device);
400
531
 
401
532
  // release transport before new transport instance
402
533
  await this.release(uuid);
@@ -414,23 +545,7 @@ export default class ReactNativeBleTransport {
414
545
  connectId: device.id,
415
546
  });
416
547
 
417
- transport.disconnectSubscription = device.onDisconnected(() => {
418
- try {
419
- Log?.debug('device disconnect: ', device?.id);
420
- this.emitter?.emit('device-disconnect', {
421
- name: device?.name,
422
- id: device?.id,
423
- connectId: device?.id,
424
- });
425
- if (this.runPromise) {
426
- this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
427
- }
428
- } catch (e) {
429
- Log?.debug('device disconnect error: ', e);
430
- } finally {
431
- this.release(uuid);
432
- }
433
- });
548
+ this.attachDisconnectSubscription(transport, device, uuid);
434
549
 
435
550
  return { uuid };
436
551
  }
@@ -445,6 +560,10 @@ export default class ReactNativeBleTransport {
445
560
  error as unknown as string
446
561
  }`
447
562
  );
563
+ if (this.firmwareUploadWriteRecoveryIds.has(uuid)) {
564
+ Log?.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
565
+ return;
566
+ }
448
567
  if (this.runPromise) {
449
568
  let ERROR:
450
569
  | typeof HardwareErrorCode.BleDeviceBondError
@@ -610,6 +729,41 @@ export default class ReactNativeBleTransport {
610
729
  }
611
730
  }
612
731
 
732
+ async function writeFirmwareUploadChunkedData(
733
+ buffers: ByteBuffer[],
734
+ writeFunction: (data: string) => Promise<void>,
735
+ onError: (e: any) => void
736
+ ) {
737
+ let index = 0;
738
+ let packetsWritten = 0;
739
+ let chunk = ByteBuffer.allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
740
+
741
+ while (index < buffers.length) {
742
+ const buffer = buffers[index].toBuffer();
743
+ chunk.append(buffer);
744
+ index += 1;
745
+
746
+ if (chunk.offset === FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY || index >= buffers.length) {
747
+ chunk.reset();
748
+ try {
749
+ await writeFunction(chunk.toString('base64'));
750
+ packetsWritten += 1;
751
+ chunk = ByteBuffer.allocate(FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY);
752
+ if (packetsWritten % FIRMWARE_UPLOAD_WRITE_BURST_SIZE === 0 && index < buffers.length) {
753
+ await delay(FIRMWARE_UPLOAD_WRITE_PAUSE_MS);
754
+ }
755
+ } catch (e) {
756
+ onError(e);
757
+ throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
758
+ }
759
+ }
760
+ }
761
+
762
+ if (packetsWritten > 0) {
763
+ await delay(FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS);
764
+ }
765
+ }
766
+
613
767
  if (name === 'EmmcFileWrite') {
614
768
  await writeChunkedData(
615
769
  buffers,
@@ -620,10 +774,57 @@ export default class ReactNativeBleTransport {
620
774
  }
621
775
  );
622
776
  } else if (name === 'FirmwareUpload') {
623
- await writeChunkedData(
777
+ Log?.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
778
+ packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
779
+ burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
780
+ pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
781
+ flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
782
+ maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
783
+ });
784
+
785
+ await writeFirmwareUploadChunkedData(
624
786
  buffers,
625
787
  async data => {
626
- await transport.writeCharacteristic.writeWithoutResponse(data);
788
+ let attempt = 0;
789
+ // Retry only congestion. Other write errors should surface immediately.
790
+ // GATT_CONGESTED is usually transient backpressure from the Android BLE queue.
791
+ // eslint-disable-next-line no-constant-condition
792
+ while (true) {
793
+ try {
794
+ await transport.writeCharacteristic.writeWithoutResponse(data);
795
+ return;
796
+ } catch (error) {
797
+ const retryType = getFirmwareUploadWriteRetryType(error);
798
+ if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
799
+ throw error;
800
+ }
801
+ const shouldReconnect = retryType === 'reconnectable';
802
+ const delayMs = shouldReconnect
803
+ ? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
804
+ : resolveFirmwareUploadRetryDelay(attempt);
805
+ Log?.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
806
+ attempt: attempt + 1,
807
+ delayMs,
808
+ reconnect: shouldReconnect,
809
+ error,
810
+ });
811
+ if (shouldReconnect) {
812
+ this.firmwareUploadWriteRecoveryIds.add(uuid);
813
+ }
814
+ await delay(delayMs);
815
+ attempt += 1;
816
+ if (shouldReconnect) {
817
+ try {
818
+ await this.reconnectFirmwareUploadTransport(uuid, transport);
819
+ } catch (e) {
820
+ Log?.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
821
+ if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
822
+ throw e;
823
+ }
824
+ }
825
+ }
826
+ }
827
+ }
627
828
  },
628
829
  e => {
629
830
  this.runPromise = null;