@ledgerhq/react-native-hw-transport-ble 6.33.4 → 6.34.0-next.0

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.
@@ -1,9 +1,9 @@
1
1
  import { v4 as uuid } from "uuid";
2
- import Transport from "@ledgerhq/hw-transport";
3
2
  import type {
4
- Subscription as TransportSubscription,
5
3
  Observer as TransportObserver,
4
+ Subscription as TransportSubscription,
6
5
  } from "@ledgerhq/hw-transport";
6
+ import Transport from "@ledgerhq/hw-transport";
7
7
  // ---------------------------------------------------------------------------------------------
8
8
  // Since this is a react-native library and metro bundler does not support
9
9
  // package exports yet (see: https://github.com/facebook/metro/issues/670)
@@ -15,64 +15,62 @@ import type {
15
15
  import { sendAPDU } from "@ledgerhq/devices/lib/ble/sendAPDU";
16
16
  import { receiveAPDU } from "@ledgerhq/devices/lib/ble/receiveAPDU";
17
17
  import {
18
- BleManager,
19
- ConnectionPriority,
18
+ BleError,
20
19
  BleErrorCode,
21
- LogLevel,
22
- DeviceId,
23
- Device,
24
20
  Characteristic,
25
- BleError,
21
+ ConnectionPriority,
22
+ Device,
23
+ DeviceId,
26
24
  Subscription,
27
25
  } from "react-native-ble-plx";
26
+ import type { DeviceModel } from "@ledgerhq/devices";
28
27
  import {
29
28
  BluetoothInfos,
30
29
  DeviceModelId,
31
30
  getBluetoothServiceUuids,
32
31
  getInfosForServiceUuid,
33
32
  } from "@ledgerhq/devices";
34
- import type { DeviceModel } from "@ledgerhq/devices";
35
- import { trace, LocalTracer, TraceContext } from "@ledgerhq/logs";
33
+ import { LocalTracer, trace, TraceContext } from "@ledgerhq/logs";
36
34
  import {
37
- Observable,
38
35
  defer,
39
- merge,
36
+ firstValueFrom,
40
37
  from,
38
+ merge,
39
+ Observable,
40
+ Observer,
41
41
  of,
42
+ SchedulerLike,
42
43
  throwError,
43
- Observer,
44
- firstValueFrom,
45
44
  TimeoutError,
46
- SchedulerLike,
47
45
  } from "rxjs";
48
46
  import {
49
- share,
50
- ignoreElements,
47
+ catchError,
48
+ finalize,
51
49
  first,
50
+ ignoreElements,
52
51
  map,
52
+ share,
53
53
  tap,
54
- catchError,
55
54
  timeout,
56
- finalize,
57
55
  } from "rxjs/operators";
58
56
  import {
59
57
  CantOpenDevice,
60
- TransportError,
61
58
  DisconnectedDeviceDuringOperation,
59
+ HwTransportError,
62
60
  PairingFailed,
63
61
  PeerRemovedPairing,
64
- HwTransportError,
62
+ TransportError,
65
63
  TransportExchangeTimeoutError,
66
64
  } from "@ledgerhq/errors";
67
65
  import { monitorCharacteristic } from "./monitorCharacteristic";
68
- import { awaitsBleOn } from "./awaitsBleOn";
69
66
  import {
70
67
  decoratePromiseErrors,
71
- remapError,
72
- mapBleErrorToHwTransportError,
73
68
  IOBleErrorRemap,
69
+ mapBleErrorToHwTransportError,
70
+ remapError,
74
71
  } from "./remapErrors";
75
72
  import { ReconnectionConfig } from "./types";
73
+ import { BlePlxManager } from "./BlePlxManager";
76
74
 
77
75
  const LOG_TYPE = "ble-verbose";
78
76
 
@@ -120,22 +118,6 @@ let connectOptions: Record<string, unknown> = {
120
118
  connectionPriority: 1,
121
119
  };
122
120
 
123
- /**
124
- * Returns the instance of the Bluetooth Low Energy Manager. It initializes it only
125
- * when it's first needed, preventing the permission prompt happening prematurely.
126
- * Important: Do NOT access the _bleManager variable directly.
127
- * Use this function instead.
128
- * @returns {BleManager} - The instance of the BleManager.
129
- */
130
- let _bleManager: BleManager | null = null;
131
- const bleManagerInstance = (): BleManager => {
132
- if (!_bleManager) {
133
- _bleManager = new BleManager();
134
- }
135
-
136
- return _bleManager;
137
- };
138
-
139
121
  const clearDisconnectTimeout = (deviceId: string, context?: TraceContext): void => {
140
122
  const cachedTransport = transportsCache[deviceId];
141
123
  if (cachedTransport && cachedTransport.disconnectTimeout) {
@@ -177,20 +159,17 @@ async function open(
177
159
  }
178
160
 
179
161
  tracer.trace(`Trying to open device: ${deviceOrId}`);
180
- await awaitsBleOn(bleManagerInstance());
162
+ await BlePlxManager.waitOn();
181
163
 
182
164
  // Returns a list of known devices by their identifiers
183
- const devices = await bleManagerInstance().devices([deviceOrId]);
184
- tracer.trace(`Found ${devices.length} already known device(s) with given id`, { deviceOrId });
185
- [device] = devices;
165
+ device = await BlePlxManager.getKnownDevice(deviceOrId);
186
166
 
187
167
  if (!device) {
168
+ tracer.trace(`Found already known device with given id`, { deviceOrId });
188
169
  // Returns a list of the peripherals currently connected to the system
189
170
  // which have discovered services, connected to system doesn't mean
190
171
  // connected to our app, we check that below.
191
- const connectedDevices = await bleManagerInstance().connectedDevices(
192
- getBluetoothServiceUuids(),
193
- );
172
+ const connectedDevices = await BlePlxManager.getConnectedDevices();
194
173
  const connectedDevicesFiltered = connectedDevices.filter(d => d.id === deviceOrId);
195
174
  tracer.trace(
196
175
  `No known device with given id. Found ${connectedDevicesFiltered.length} devices from already connected devices`,
@@ -208,7 +187,7 @@ async function open(
208
187
 
209
188
  // Nb ConnectionOptions dropped since it's not used internally by ble-plx.
210
189
  try {
211
- device = await bleManagerInstance().connectToDevice(deviceOrId, {
190
+ device = await BlePlxManager.connect(deviceOrId, {
212
191
  ...connectOptions,
213
192
  timeout: timeoutMs,
214
193
  });
@@ -217,7 +196,7 @@ async function open(
217
196
  if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
218
197
  // If the MTU update did not work, we try to connect without requesting for a specific MTU
219
198
  connectOptions = {};
220
- device = await bleManagerInstance().connectToDevice(deviceOrId, { timeout: timeoutMs });
199
+ device = await BlePlxManager.connect(deviceOrId, { timeout: timeoutMs });
221
200
  } else {
222
201
  throw e;
223
202
  }
@@ -457,7 +436,7 @@ export default class BleTransport extends Transport {
457
436
  /**
458
437
  *
459
438
  */
460
- static isSupported = (): Promise<boolean> => Promise.resolve(typeof BleManager === "function");
439
+ static isSupported = (): Promise<boolean> => Promise.resolve(typeof BlePlxManager === "function");
461
440
 
462
441
  /**
463
442
  *
@@ -466,21 +445,15 @@ export default class BleTransport extends Transport {
466
445
  throw new Error("not implemented");
467
446
  };
468
447
 
469
- /**
470
- * Exposed method from the ble-plx library
471
- * Sets new log level for native module's logging mechanism.
472
- * @param string logLevel New log level to be set.
473
- */
474
- static setLogLevel = (logLevel: string): void => {
475
- if (Object.values<string>(LogLevel).includes(logLevel)) {
476
- bleManagerInstance().setLogLevel(logLevel as LogLevel);
477
- } else {
478
- throw new Error(`${logLevel} is not a valid LogLevel`);
479
- }
480
- };
448
+ // /**
449
+ // * Exposed method from the ble-plx library
450
+ // * Sets new log level for native module's logging mechanism.
451
+ // * @param string logLevel New log level to be set.
452
+ // */
453
+ static setLogLevel = BlePlxManager.setLogLevel;
481
454
 
482
455
  /**
483
- * Listen to state changes on the bleManagerInstance and notify the
456
+ * Listen to state changes on the BlePlxManagerInstance and notify the
484
457
  * specified observer.
485
458
  * @param observer
486
459
  * @returns TransportSubscription
@@ -498,16 +471,25 @@ export default class BleTransport extends Transport {
498
471
  });
499
472
  };
500
473
 
501
- bleManagerInstance().onStateChange(emitFromState, true);
474
+ BlePlxManager.onStateChange(emitFromState, true);
502
475
 
503
476
  return {
504
477
  unsubscribe: () => {},
505
478
  };
506
479
  }
507
480
 
481
+ static safeRemove = (sub: Subscription, tracer: LocalTracer) => {
482
+ try {
483
+ sub.remove();
484
+ } catch (error) {
485
+ tracer.trace("Error removing state subscription", { error });
486
+ }
487
+ };
488
+
508
489
  /**
509
490
  * Scan for bluetooth Ledger devices
510
491
  * @param observer Device is partial in order to avoid the live-common/this dep
492
+ * @param context
511
493
  * @returns TransportSubscription
512
494
  */
513
495
  static listen(
@@ -519,49 +501,55 @@ export default class BleTransport extends Transport {
519
501
 
520
502
  let unsubscribed: boolean;
521
503
 
522
- const stateSub = bleManagerInstance().onStateChange(async state => {
504
+ const stateSub = BlePlxManager.onStateChange(async state => {
523
505
  if (state === "PoweredOn") {
524
- stateSub.remove();
525
- const devices = await bleManagerInstance().connectedDevices(getBluetoothServiceUuids());
506
+ BleTransport.safeRemove(stateSub, tracer);
507
+ const devices = await BlePlxManager.getConnectedDevices().catch(err => {
508
+ // Handle possible connection errors
509
+ tracer.trace("Error while fetching connected devices", { err });
510
+ return [];
511
+ });
526
512
  if (unsubscribed) return;
527
513
  if (devices.length) {
528
514
  tracer.trace("Disconnecting from all devices", { deviceCount: devices.length });
529
515
 
530
- await Promise.all(devices.map(d => BleTransport.disconnectDevice(d.id)));
516
+ try {
517
+ await Promise.all(devices.map(d => BleTransport.disconnectDevice(d.id)));
518
+ } catch (error) {
519
+ tracer.trace("Error disconnecting some devices", { error });
520
+ }
531
521
  }
532
522
 
533
523
  if (unsubscribed) return;
534
- bleManagerInstance().startDeviceScan(
535
- getBluetoothServiceUuids(),
536
- null,
537
- (bleError: BleError | null, scannedDevice: Device | null) => {
538
- if (bleError) {
539
- observer.error(mapBleErrorToHwTransportError(bleError));
540
- unsubscribe();
541
- return;
542
- }
543
-
544
- const res = retrieveInfos(scannedDevice);
545
- const deviceModel = res && res.deviceModel;
546
-
547
- if (scannedDevice) {
548
- observer.next({
549
- type: "add",
550
- descriptor: scannedDevice,
551
- deviceModel,
552
- });
553
- }
554
- },
555
- );
524
+ await BlePlxManager.startScan((bleError: BleError | null, scannedDevice: Device | null) => {
525
+ if (bleError) {
526
+ tracer.trace("Listening startDeviceScan error", { scannedDevice, bleError });
527
+ observer.error(mapBleErrorToHwTransportError(bleError));
528
+ unsubscribe();
529
+ return;
530
+ }
531
+
532
+ const res = retrieveInfos(scannedDevice);
533
+ const deviceModel = res && res.deviceModel;
534
+
535
+ if (scannedDevice) {
536
+ observer.next({
537
+ type: "add",
538
+ descriptor: scannedDevice,
539
+ deviceModel,
540
+ });
541
+ }
542
+ });
556
543
  }
557
544
  }, true);
558
545
 
559
546
  const unsubscribe = () => {
547
+ if (unsubscribed) return;
560
548
  unsubscribed = true;
561
- bleManagerInstance().stopDeviceScan();
562
- stateSub.remove();
563
-
564
- tracer.trace("Done listening");
549
+ BlePlxManager.stopScan().then(() => {
550
+ BleTransport.safeRemove(stateSub, tracer);
551
+ tracer.trace("Done listening");
552
+ });
565
553
  };
566
554
 
567
555
  return {
@@ -598,14 +586,12 @@ export default class BleTransport extends Transport {
598
586
  const tracer = new LocalTracer(LOG_TYPE, context);
599
587
  tracer.trace(`Trying to disconnect device ${id}`);
600
588
 
601
- await bleManagerInstance()
602
- .cancelDeviceConnection(id)
603
- .catch(error => {
604
- // Only log, ignore if disconnect did not work
605
- tracer
606
- .withType("ble-error")
607
- .trace(`Error while trying to cancel device connection`, { error });
608
- });
589
+ await BlePlxManager.disconnectDevice(id).catch(error => {
590
+ // Only log, ignore if disconnect did not work
591
+ tracer
592
+ .withType("ble-error")
593
+ .trace(`Error while trying to cancel device connection`, { error });
594
+ });
609
595
  tracer.trace(`Device ${id} disconnected`);
610
596
  };
611
597
 
@@ -711,7 +697,7 @@ export default class BleTransport extends Transport {
711
697
  );
712
698
 
713
699
  // No concurrent exchange should happen at the same time, so all pending operations are part of the same exchange
714
- this.cancelPendingOperations();
700
+ await this.cancelPendingOperations();
715
701
 
716
702
  throw new TransportExchangeTimeoutError("Exchange aborted due to timeout");
717
703
  }
@@ -750,11 +736,11 @@ export default class BleTransport extends Transport {
750
736
  * but this error should be ignored. (In `exchange` our observable is unsubscribed before `cancelPendingOperations`
751
737
  * is called so the error is ignored)
752
738
  */
753
- private cancelPendingOperations() {
739
+ private async cancelPendingOperations() {
754
740
  for (const transactionId of this.currentTransactionIds) {
755
741
  try {
756
742
  this.tracer.trace("Cancelling operation", { transactionId });
757
- bleManagerInstance().cancelTransaction(transactionId);
743
+ await BlePlxManager.cancelTransaction(transactionId);
758
744
  } catch (error) {
759
745
  this.tracer.trace("Error while cancelling operation", { transactionId, error });
760
746
  }