@ledgerhq/react-native-hw-transport-ble 6.34.0 → 6.34.1-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,13 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- var _a;
11
1
  import { v4 as uuid } from "uuid";
12
2
  import Transport from "@ledgerhq/hw-transport";
13
3
  // ---------------------------------------------------------------------------------------------
@@ -88,246 +78,264 @@ const clearDisconnectTimeout = (deviceId, context) => {
88
78
  * - rxjsScheduler: dependency injected RxJS scheduler to control time. Default AsyncScheduler.
89
79
  * @returns A BleTransport instance
90
80
  */
91
- function open(deviceOrId_1, needsReconnect_1, timeoutMs_1, context_1) {
92
- return __awaiter(this, arguments, void 0, function* (deviceOrId, needsReconnect, timeoutMs, context, { rxjsScheduler } = {}) {
93
- const tracer = new LocalTracer(LOG_TYPE, context);
94
- let device;
95
- tracer.trace(`Opening ${deviceOrId}`, { needsReconnect });
96
- if (typeof deviceOrId === "string") {
97
- if (transportsCache[deviceOrId]) {
98
- tracer.trace(`Transport in cache, using it`);
99
- clearDisconnectTimeout(deviceOrId);
100
- // The cached transport probably has an older trace/log context
101
- transportsCache[deviceOrId].setTraceContext(context);
102
- return transportsCache[deviceOrId];
103
- }
104
- tracer.trace(`Trying to open device: ${deviceOrId}`);
105
- yield BlePlxManager.waitOn();
106
- // Returns a list of known devices by their identifiers
107
- device = yield BlePlxManager.getKnownDevice(deviceOrId);
108
- if (!device) {
109
- tracer.trace(`Found already known device with given id`, { deviceOrId });
110
- // Returns a list of the peripherals currently connected to the system
111
- // which have discovered services, connected to system doesn't mean
112
- // connected to our app, we check that below.
113
- const connectedDevices = yield BlePlxManager.getConnectedDevices();
114
- const connectedDevicesFiltered = connectedDevices.filter(d => d.id === deviceOrId);
115
- tracer.trace(`No known device with given id. Found ${connectedDevicesFiltered.length} devices from already connected devices`, { deviceOrId });
116
- [device] = connectedDevicesFiltered;
117
- }
118
- if (!device) {
119
- // We still don't have a device, so we attempt to connect to it.
120
- tracer.trace(`No known nor connected devices with given id. Trying to connect to device`, {
121
- deviceOrId,
122
- timeoutMs,
123
- });
124
- // Nb ConnectionOptions dropped since it's not used internally by ble-plx.
125
- try {
126
- device = yield BlePlxManager.connect(deviceOrId, Object.assign(Object.assign({}, connectOptions), { timeout: timeoutMs }));
127
- }
128
- catch (e) {
129
- tracer.trace(`Error code: ${e.errorCode}`);
130
- if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
131
- // If the MTU update did not work, we try to connect without requesting for a specific MTU
132
- connectOptions = {};
133
- device = yield BlePlxManager.connect(deviceOrId, { timeout: timeoutMs });
134
- }
135
- else {
136
- throw e;
137
- }
138
- }
139
- }
140
- if (!device) {
141
- throw new CantOpenDevice();
142
- }
81
+ async function open(deviceOrId, needsReconnect, timeoutMs, context, { rxjsScheduler } = {}) {
82
+ const tracer = new LocalTracer(LOG_TYPE, context);
83
+ let device;
84
+ tracer.trace(`Opening ${deviceOrId}`, { needsReconnect });
85
+ if (typeof deviceOrId === "string") {
86
+ if (transportsCache[deviceOrId]) {
87
+ tracer.trace(`Transport in cache, using it`);
88
+ clearDisconnectTimeout(deviceOrId);
89
+ // The cached transport probably has an older trace/log context
90
+ transportsCache[deviceOrId].setTraceContext(context);
91
+ return transportsCache[deviceOrId];
143
92
  }
144
- else {
145
- // It was already a Device
146
- device = deviceOrId;
93
+ tracer.trace(`Trying to open device: ${deviceOrId}`);
94
+ await BlePlxManager.waitOn();
95
+ // Returns a list of known devices by their identifiers
96
+ device = await BlePlxManager.getKnownDevice(deviceOrId);
97
+ if (!device) {
98
+ tracer.trace(`Found already known device with given id`, { deviceOrId });
99
+ // Returns a list of the peripherals currently connected to the system
100
+ // which have discovered services, connected to system doesn't mean
101
+ // connected to our app, we check that below.
102
+ const connectedDevices = await BlePlxManager.getConnectedDevices();
103
+ const connectedDevicesFiltered = connectedDevices.filter(d => d.id === deviceOrId);
104
+ tracer.trace(`No known device with given id. Found ${connectedDevicesFiltered.length} devices from already connected devices`, { deviceOrId });
105
+ [device] = connectedDevicesFiltered;
147
106
  }
148
- if (!(yield device.isConnected())) {
149
- tracer.trace(`Device found but not connected. connecting...`, { timeoutMs, connectOptions });
107
+ if (!device) {
108
+ // We still don't have a device, so we attempt to connect to it.
109
+ tracer.trace(`No known nor connected devices with given id. Trying to connect to device`, {
110
+ deviceOrId,
111
+ timeoutMs,
112
+ });
113
+ // Nb ConnectionOptions dropped since it's not used internally by ble-plx.
150
114
  try {
151
- yield device.connect(Object.assign(Object.assign({}, connectOptions), { timeout: timeoutMs }));
115
+ device = await BlePlxManager.connect(deviceOrId, {
116
+ ...connectOptions,
117
+ timeout: timeoutMs,
118
+ });
152
119
  }
153
- catch (error) {
154
- tracer.trace(`Connect error`, { error });
155
- if (error.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
156
- tracer.trace(`Device mtu=${device.mtu}, reconnecting`);
120
+ catch (e) {
121
+ tracer.trace(`Error code: ${e.errorCode}`);
122
+ if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
123
+ // If the MTU update did not work, we try to connect without requesting for a specific MTU
157
124
  connectOptions = {};
158
- yield device.connect();
159
- }
160
- else if (error.iosErrorCode === 14 || error.reason === "Peer removed pairing information") {
161
- tracer.trace(`iOS broken pairing`, {
162
- device,
163
- bluetoothInfoCache: bluetoothInfoCache[device.id],
164
- });
165
- const { deviceModel } = bluetoothInfoCache[device.id] || {};
166
- const { productName } = deviceModel || {};
167
- throw new PeerRemovedPairing(undefined, {
168
- deviceName: device.name,
169
- productName,
170
- });
171
- }
172
- // This case should not happen, but recording logs to be warned if we see it in production
173
- else if (error.errorCode === BleErrorCode.DeviceAlreadyConnected) {
174
- tracer.trace(`Device already connected, while it was not supposed to`, {
175
- error,
176
- });
177
- throw remapError(error);
125
+ device = await BlePlxManager.connect(deviceOrId, { timeout: timeoutMs });
178
126
  }
179
127
  else {
180
- throw remapError(error);
128
+ throw e;
181
129
  }
182
130
  }
183
131
  }
184
- tracer.trace(`Device is connected now, getting services and characteristics`);
185
- yield device.discoverAllServicesAndCharacteristics();
186
- let res = retrieveInfos(device);
187
- let characteristics;
188
- if (!res) {
189
- for (const uuid of getBluetoothServiceUuids()) {
190
- try {
191
- characteristics = yield device.characteristicsForService(uuid);
192
- res = getInfosForServiceUuid(uuid);
193
- break;
194
- }
195
- catch (e) {
196
- // we attempt to connect to service
197
- }
198
- }
132
+ if (!device) {
133
+ throw new CantOpenDevice();
199
134
  }
200
- if (!res) {
201
- tracer.trace(`Service not found`);
202
- throw new TransportError("service not found", "BLEServiceNotFound");
203
- }
204
- const { deviceModel, serviceUuid, writeUuid, writeCmdUuid, notifyUuid } = res;
205
- if (!characteristics) {
206
- characteristics = yield device.characteristicsForService(serviceUuid);
207
- }
208
- if (!characteristics) {
209
- tracer.trace(`Characteristics not found`);
210
- throw new TransportError("service not found", "BLEServiceNotFound");
135
+ }
136
+ else {
137
+ // It was already a Device
138
+ device = deviceOrId;
139
+ }
140
+ if (!(await device.isConnected())) {
141
+ tracer.trace(`Device found but not connected. connecting...`, { timeoutMs, connectOptions });
142
+ try {
143
+ await device.connect({ ...connectOptions, timeout: timeoutMs });
211
144
  }
212
- let writableWithResponseCharacteristic;
213
- let writableWithoutResponseCharacteristic;
214
- // A characteristic that can monitor value changes
215
- let notifiableCharacteristic;
216
- for (const c of characteristics) {
217
- if (c.uuid === writeUuid) {
218
- writableWithResponseCharacteristic = c;
145
+ catch (error) {
146
+ tracer.trace(`Connect error`, { error });
147
+ if (error.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
148
+ tracer.trace(`Device mtu=${device.mtu}, reconnecting`);
149
+ connectOptions = {};
150
+ await device.connect();
151
+ }
152
+ else if (error.iosErrorCode === 14 || error.reason === "Peer removed pairing information") {
153
+ tracer.trace(`iOS broken pairing`, {
154
+ device,
155
+ bluetoothInfoCache: bluetoothInfoCache[device.id],
156
+ });
157
+ const { deviceModel } = bluetoothInfoCache[device.id] || {};
158
+ const { productName } = deviceModel || {};
159
+ throw new PeerRemovedPairing(undefined, {
160
+ deviceName: device.name,
161
+ productName,
162
+ });
219
163
  }
220
- else if (c.uuid === writeCmdUuid) {
221
- writableWithoutResponseCharacteristic = c;
164
+ // This case should not happen, but recording logs to be warned if we see it in production
165
+ else if (error.errorCode === BleErrorCode.DeviceAlreadyConnected) {
166
+ tracer.trace(`Device already connected, while it was not supposed to`, {
167
+ error,
168
+ });
169
+ throw remapError(error);
222
170
  }
223
- else if (c.uuid === notifyUuid) {
224
- notifiableCharacteristic = c;
171
+ else {
172
+ throw remapError(error);
225
173
  }
226
174
  }
227
- if (!writableWithResponseCharacteristic) {
228
- throw new TransportError("write characteristic not found", "BLECharacteristicNotFound");
229
- }
230
- if (!notifiableCharacteristic) {
231
- throw new TransportError("notify characteristic not found", "BLECharacteristicNotFound");
175
+ }
176
+ tracer.trace(`Device is connected now, getting services and characteristics`);
177
+ await device.discoverAllServicesAndCharacteristics();
178
+ let res = retrieveInfos(device);
179
+ let characteristics;
180
+ if (!res) {
181
+ for (const uuid of getBluetoothServiceUuids()) {
182
+ try {
183
+ characteristics = await device.characteristicsForService(uuid);
184
+ res = getInfosForServiceUuid(uuid);
185
+ break;
186
+ }
187
+ catch (e) {
188
+ // we attempt to connect to service
189
+ }
232
190
  }
233
- if (!writableWithResponseCharacteristic.isWritableWithResponse) {
234
- throw new TransportError("The writable-with-response characteristic is not writable with response", "BLECharacteristicInvalid");
191
+ }
192
+ if (!res) {
193
+ tracer.trace(`Service not found`);
194
+ throw new TransportError("service not found", "BLEServiceNotFound");
195
+ }
196
+ const { deviceModel, serviceUuid, writeUuid, writeCmdUuid, notifyUuid } = res;
197
+ if (!characteristics) {
198
+ characteristics = await device.characteristicsForService(serviceUuid);
199
+ }
200
+ if (!characteristics) {
201
+ tracer.trace(`Characteristics not found`);
202
+ throw new TransportError("service not found", "BLEServiceNotFound");
203
+ }
204
+ let writableWithResponseCharacteristic;
205
+ let writableWithoutResponseCharacteristic;
206
+ // A characteristic that can monitor value changes
207
+ let notifiableCharacteristic;
208
+ for (const c of characteristics) {
209
+ if (c.uuid === writeUuid) {
210
+ writableWithResponseCharacteristic = c;
235
211
  }
236
- if (!notifiableCharacteristic.isNotifiable) {
237
- throw new TransportError("notify characteristic not notifiable", "BLECharacteristicInvalid");
212
+ else if (c.uuid === writeCmdUuid) {
213
+ writableWithoutResponseCharacteristic = c;
238
214
  }
239
- if (writableWithoutResponseCharacteristic) {
240
- if (!writableWithoutResponseCharacteristic.isWritableWithoutResponse) {
241
- throw new TransportError("The writable-without-response characteristic is not writable without response", "BLECharacteristicInvalid");
242
- }
215
+ else if (c.uuid === notifyUuid) {
216
+ notifiableCharacteristic = c;
243
217
  }
244
- tracer.trace(`device.mtu=${device.mtu}`);
245
- // Inits the observable that will emit received data from the device via BLE
246
- const notifyObservable = monitorCharacteristic(notifiableCharacteristic, context).pipe(catchError(e => {
247
- // LL-9033 fw 2.0.2 introduced this case, we silence the inner unhandled error.
248
- // It will be handled when negotiating the MTU in `inferMTU` but will be ignored in other cases.
249
- const msg = String(e);
250
- return msg.includes("notify change failed")
251
- ? of(new PairingFailed(msg))
252
- : throwError(() => e);
253
- }), tap(value => {
254
- if (value instanceof PairingFailed)
255
- return;
256
- trace({ type: "ble-frame", message: `<= ${value.toString("hex")}`, context });
257
- }),
258
- // Returns a new Observable that multicasts (shares) the original Observable.
259
- // As long as there is at least one Subscriber this Observable will be subscribed and emitting data.
260
- share());
261
- // Keeps the input from the device observable alive (multicast observable)
262
- const notif = notifyObservable.subscribe();
263
- const transport = new BleTransport(device, writableWithResponseCharacteristic, writableWithoutResponseCharacteristic, notifyObservable, deviceModel, {
264
- context,
265
- rxjsScheduler,
266
- });
267
- tracer.trace(`New BleTransport created`);
268
- // Keeping it as a comment for now but if no new bluetooth issues occur, we will be able to remove it
269
- // await transport.requestConnectionPriority("High");
270
- // eslint-disable-next-line prefer-const
271
- let disconnectedSub;
272
- // Callbacks on `react-native-ble-plx` notifying the device has been disconnected
273
- const onDisconnect = (error) => {
274
- transport.isConnected = false;
275
- transport.notYetDisconnected = false;
276
- notif.unsubscribe();
277
- disconnectedSub === null || disconnectedSub === void 0 ? void 0 : disconnectedSub.remove();
278
- clearDisconnectTimeout(transport.id);
279
- delete transportsCache[transport.id];
280
- tracer.trace(`On device disconnected callback: cleared cached transport for ${transport.id}, emitting Transport event "disconnect. Error: ${error}"`, { reason: error });
281
- transport.emit("disconnect", error);
282
- };
283
- // eslint-disable-next-line require-atomic-updates
284
- transportsCache[transport.id] = transport;
285
- const beforeMTUTime = Date.now();
286
- disconnectedSub = device.onDisconnected(e => {
287
- if (!transport.notYetDisconnected)
288
- return;
289
- onDisconnect(e);
290
- });
291
- try {
292
- yield transport.inferMTU();
218
+ }
219
+ if (!writableWithResponseCharacteristic) {
220
+ throw new TransportError("write characteristic not found", "BLECharacteristicNotFound");
221
+ }
222
+ if (!notifiableCharacteristic) {
223
+ throw new TransportError("notify characteristic not found", "BLECharacteristicNotFound");
224
+ }
225
+ if (!writableWithResponseCharacteristic.isWritableWithResponse) {
226
+ throw new TransportError("The writable-with-response characteristic is not writable with response", "BLECharacteristicInvalid");
227
+ }
228
+ if (!notifiableCharacteristic.isNotifiable) {
229
+ throw new TransportError("notify characteristic not notifiable", "BLECharacteristicInvalid");
230
+ }
231
+ if (writableWithoutResponseCharacteristic) {
232
+ if (!writableWithoutResponseCharacteristic.isWritableWithoutResponse) {
233
+ throw new TransportError("The writable-without-response characteristic is not writable without response", "BLECharacteristicInvalid");
293
234
  }
294
- finally {
295
- const afterMTUTime = Date.now();
296
- if (reconnectionConfig) {
297
- // Refer to ledgerjs archived repo issue #279
298
- // All HW .v1 LNX have a bug that prevents us from communicating with the device right after pairing.
299
- // When we connect for the first time we issue a disconnect and reconnect, this guarantees that we are
300
- // in a good state. This is avoidable in some key scenarios ↓
301
- if (afterMTUTime - beforeMTUTime < reconnectionConfig.pairingThreshold) {
302
- needsReconnect = false;
303
- }
304
- else if (deviceModel.id === DeviceModelId.stax) {
305
- tracer.trace(`Skipping "needsReconnect" strategy for Stax`);
306
- needsReconnect = false;
307
- }
308
- if (needsReconnect) {
309
- tracer.trace(`Device needs reconnection. Triggering a disconnect`);
310
- yield BleTransport.disconnectDevice(transport.id);
311
- yield delay(reconnectionConfig.delayAfterFirstPairing);
312
- }
235
+ }
236
+ tracer.trace(`device.mtu=${device.mtu}`);
237
+ // Inits the observable that will emit received data from the device via BLE
238
+ const notifyObservable = monitorCharacteristic(notifiableCharacteristic, context).pipe(catchError(e => {
239
+ // LL-9033 fw 2.0.2 introduced this case, we silence the inner unhandled error.
240
+ // It will be handled when negotiating the MTU in `inferMTU` but will be ignored in other cases.
241
+ const msg = String(e);
242
+ return msg.includes("notify change failed")
243
+ ? of(new PairingFailed(msg))
244
+ : throwError(() => e);
245
+ }), tap(value => {
246
+ if (value instanceof PairingFailed)
247
+ return;
248
+ trace({ type: "ble-frame", message: `<= ${value.toString("hex")}`, context });
249
+ }),
250
+ // Returns a new Observable that multicasts (shares) the original Observable.
251
+ // As long as there is at least one Subscriber this Observable will be subscribed and emitting data.
252
+ share());
253
+ // Keeps the input from the device observable alive (multicast observable)
254
+ const notif = notifyObservable.subscribe();
255
+ const transport = new BleTransport(device, writableWithResponseCharacteristic, writableWithoutResponseCharacteristic, notifyObservable, deviceModel, {
256
+ context,
257
+ rxjsScheduler,
258
+ });
259
+ tracer.trace(`New BleTransport created`);
260
+ // Keeping it as a comment for now but if no new bluetooth issues occur, we will be able to remove it
261
+ // await transport.requestConnectionPriority("High");
262
+ // eslint-disable-next-line prefer-const
263
+ let disconnectedSub;
264
+ // Callbacks on `react-native-ble-plx` notifying the device has been disconnected
265
+ const onDisconnect = (error) => {
266
+ transport.isConnected = false;
267
+ transport.notYetDisconnected = false;
268
+ notif.unsubscribe();
269
+ disconnectedSub?.remove();
270
+ clearDisconnectTimeout(transport.id);
271
+ delete transportsCache[transport.id];
272
+ tracer.trace(`On device disconnected callback: cleared cached transport for ${transport.id}, emitting Transport event "disconnect. Error: ${error}"`, { reason: error });
273
+ transport.emit("disconnect", error);
274
+ };
275
+ // eslint-disable-next-line require-atomic-updates
276
+ transportsCache[transport.id] = transport;
277
+ const beforeMTUTime = Date.now();
278
+ disconnectedSub = device.onDisconnected(e => {
279
+ if (!transport.notYetDisconnected)
280
+ return;
281
+ onDisconnect(e);
282
+ });
283
+ try {
284
+ await transport.inferMTU();
285
+ }
286
+ finally {
287
+ const afterMTUTime = Date.now();
288
+ if (reconnectionConfig) {
289
+ // Refer to ledgerjs archived repo issue #279
290
+ // All HW .v1 LNX have a bug that prevents us from communicating with the device right after pairing.
291
+ // When we connect for the first time we issue a disconnect and reconnect, this guarantees that we are
292
+ // in a good state. This is avoidable in some key scenarios ↓
293
+ if (afterMTUTime - beforeMTUTime < reconnectionConfig.pairingThreshold) {
294
+ needsReconnect = false;
313
295
  }
314
- else {
296
+ else if (deviceModel.id === DeviceModelId.stax) {
297
+ tracer.trace(`Skipping "needsReconnect" strategy for Stax`);
315
298
  needsReconnect = false;
316
299
  }
300
+ if (needsReconnect) {
301
+ tracer.trace(`Device needs reconnection. Triggering a disconnect`);
302
+ await BleTransport.disconnectDevice(transport.id);
303
+ await delay(reconnectionConfig.delayAfterFirstPairing);
304
+ }
317
305
  }
318
- if (needsReconnect) {
319
- tracer.trace(`Reconnecting`);
320
- return open(device, false, timeoutMs, context);
306
+ else {
307
+ needsReconnect = false;
321
308
  }
322
- return transport;
323
- });
309
+ }
310
+ if (needsReconnect) {
311
+ tracer.trace(`Reconnecting`);
312
+ return open(device, false, timeoutMs, context);
313
+ }
314
+ return transport;
324
315
  }
325
316
  /**
326
317
  * react-native bluetooth BLE implementation
327
318
  * @example
328
319
  * import BleTransport from "@ledgerhq/react-native-hw-transport-ble";
329
320
  */
330
- class BleTransport extends Transport {
321
+ export default class BleTransport extends Transport {
322
+ static disconnectTimeoutMs = 5000;
323
+ /**
324
+ *
325
+ */
326
+ static isSupported = () => Promise.resolve(typeof BlePlxManager === "function");
327
+ /**
328
+ *
329
+ */
330
+ static list = () => {
331
+ throw new Error("not implemented");
332
+ };
333
+ // /**
334
+ // * Exposed method from the ble-plx library
335
+ // * Sets new log level for native module's logging mechanism.
336
+ // * @param string logLevel New log level to be set.
337
+ // */
338
+ static setLogLevel = BlePlxManager.setLogLevel;
331
339
  /**
332
340
  * Listen to state changes on the BlePlxManagerInstance and notify the
333
341
  * specified observer.
@@ -346,6 +354,14 @@ class BleTransport extends Transport {
346
354
  unsubscribe: () => { },
347
355
  };
348
356
  }
357
+ static safeRemove = (sub, tracer) => {
358
+ try {
359
+ sub.remove();
360
+ }
361
+ catch (error) {
362
+ tracer.trace("Error removing state subscription", { error });
363
+ }
364
+ };
349
365
  /**
350
366
  * Scan for bluetooth Ledger devices
351
367
  * @param observer Device is partial in order to avoid the live-common/this dep
@@ -356,10 +372,10 @@ class BleTransport extends Transport {
356
372
  const tracer = new LocalTracer(LOG_TYPE, context);
357
373
  tracer.trace("Listening for devices ...");
358
374
  let unsubscribed;
359
- const stateSub = BlePlxManager.onStateChange((state) => __awaiter(this, void 0, void 0, function* () {
375
+ const stateSub = BlePlxManager.onStateChange(async (state) => {
360
376
  if (state === "PoweredOn") {
361
- _a.safeRemove(stateSub, tracer);
362
- const devices = yield BlePlxManager.getConnectedDevices().catch(err => {
377
+ BleTransport.safeRemove(stateSub, tracer);
378
+ const devices = await BlePlxManager.getConnectedDevices().catch(err => {
363
379
  // Handle possible connection errors
364
380
  tracer.trace("Error while fetching connected devices", { err });
365
381
  return [];
@@ -369,7 +385,7 @@ class BleTransport extends Transport {
369
385
  if (devices.length) {
370
386
  tracer.trace("Disconnecting from all devices", { deviceCount: devices.length });
371
387
  try {
372
- yield Promise.all(devices.map(d => _a.disconnectDevice(d.id)));
388
+ await Promise.all(devices.map(d => BleTransport.disconnectDevice(d.id)));
373
389
  }
374
390
  catch (error) {
375
391
  tracer.trace("Error disconnecting some devices", { error });
@@ -377,7 +393,7 @@ class BleTransport extends Transport {
377
393
  }
378
394
  if (unsubscribed)
379
395
  return;
380
- yield BlePlxManager.startScan((bleError, scannedDevice) => {
396
+ await BlePlxManager.startScan((bleError, scannedDevice) => {
381
397
  if (bleError) {
382
398
  tracer.trace("Listening startDeviceScan error", { scannedDevice, bleError });
383
399
  observer.error(mapBleErrorToHwTransportError(bleError));
@@ -395,13 +411,13 @@ class BleTransport extends Transport {
395
411
  }
396
412
  });
397
413
  }
398
- }), true);
414
+ }, true);
399
415
  const unsubscribe = () => {
400
416
  if (unsubscribed)
401
417
  return;
402
418
  unsubscribed = true;
403
419
  BlePlxManager.stopScan().then(() => {
404
- _a.safeRemove(stateSub, tracer);
420
+ BleTransport.safeRemove(stateSub, tracer);
405
421
  tracer.trace("Done listening");
406
422
  });
407
423
  };
@@ -418,11 +434,41 @@ class BleTransport extends Transport {
418
434
  * @param injectedDependencies Contains optional injected dependencies used by the transport implementation
419
435
  * - rxjsScheduler: dependency injected RxJS scheduler to control time. Default AsyncScheduler.
420
436
  */
421
- static open(deviceOrId_1, timeoutMs_1, context_1) {
422
- return __awaiter(this, arguments, void 0, function* (deviceOrId, timeoutMs, context, { rxjsScheduler } = {}) {
423
- return open(deviceOrId, true, timeoutMs, context, { rxjsScheduler });
424
- });
437
+ static async open(deviceOrId, timeoutMs, context, { rxjsScheduler } = {}) {
438
+ return open(deviceOrId, true, timeoutMs, context, { rxjsScheduler });
425
439
  }
440
+ /**
441
+ * Exposes method from the ble-plx library to disconnect a device
442
+ *
443
+ * Disconnects from {@link Device} if it's connected or cancels pending connection.
444
+ * A "disconnect" event will normally be emitted by the ble-plx lib once the device is disconnected.
445
+ * Errors are logged but silenced.
446
+ */
447
+ static disconnectDevice = async (id, context) => {
448
+ const tracer = new LocalTracer(LOG_TYPE, context);
449
+ tracer.trace(`Trying to disconnect device ${id}`);
450
+ await BlePlxManager.disconnectDevice(id).catch(error => {
451
+ // Only log, ignore if disconnect did not work
452
+ tracer
453
+ .withType("ble-error")
454
+ .trace(`Error while trying to cancel device connection`, { error });
455
+ });
456
+ tracer.trace(`Device ${id} disconnected`);
457
+ };
458
+ device;
459
+ deviceModel;
460
+ disconnectTimeout = null;
461
+ id;
462
+ isConnected = true;
463
+ mtuSize = 20;
464
+ // Observable emitting data received from the device via BLE
465
+ notifyObservable;
466
+ notYetDisconnected = true;
467
+ writableWithResponseCharacteristic;
468
+ writableWithoutResponseCharacteristic;
469
+ rxjsScheduler;
470
+ // Transaction ids of communication operations that are currently pending
471
+ currentTransactionIds;
426
472
  /**
427
473
  * The static `open` function is used to handle BleTransport instantiation
428
474
  *
@@ -440,102 +486,6 @@ class BleTransport extends Transport {
440
486
  */
441
487
  constructor(device, writableWithResponseCharacteristic, writableWithoutResponseCharacteristic, notifyObservable, deviceModel, { context, rxjsScheduler } = {}) {
442
488
  super({ context, logType: LOG_TYPE });
443
- this.disconnectTimeout = null;
444
- this.isConnected = true;
445
- this.mtuSize = 20;
446
- this.notYetDisconnected = true;
447
- /**
448
- * A message exchange (APDU request <-> response) with the device that can be aborted
449
- *
450
- * The message will be BLE-encoded/framed before being sent, and the response will be BLE-decoded.
451
- *
452
- * @param message A buffer (u8 array) of a none BLE-encoded message (an APDU for ex) to be sent to the device
453
- * as a request
454
- * @param options Contains optional options for the exchange function
455
- * - abortTimeoutMs: stop the exchange after a given timeout. Another timeout exists
456
- * to detect unresponsive device (see `unresponsiveTimeout`). This timeout aborts the exchange.
457
- * @returns A promise that resolves with the response data from the device.
458
- */
459
- this.exchange = (message, { abortTimeoutMs } = {}) => {
460
- const tracer = this.tracer.withUpdatedContext({
461
- function: "exchange",
462
- });
463
- tracer.trace("Exchanging APDU ...", { abortTimeoutMs });
464
- tracer.withType("apdu").trace(`=> ${message.toString("hex")}`);
465
- return this.exchangeAtomicImpl(() => {
466
- return firstValueFrom(
467
- // `sendApdu` will only emit if an error occurred, otherwise it will complete,
468
- // while `receiveAPDU` will emit the full response.
469
- // Consequently it monitors the response while being able to reject on an error from the send.
470
- merge(this.notifyObservable.pipe(data => receiveAPDU(data, { context: tracer.getContext() })), sendAPDU(this.write, message, this.mtuSize, {
471
- context: tracer.getContext(),
472
- })).pipe(abortTimeoutMs ? timeout(abortTimeoutMs, this.rxjsScheduler) : tap(), tap(data => {
473
- tracer.withType("apdu").trace(`<= ${data.toString("hex")}`);
474
- }), catchError((error) => __awaiter(this, void 0, void 0, function* () {
475
- // Currently only 1 reason the exchange has been explicitly aborted (other than job and transport errors): a timeout
476
- if (error instanceof TimeoutError) {
477
- tracer.trace("Aborting due to timeout and trying to cancel all communication write of the current exchange", {
478
- abortTimeoutMs,
479
- transactionIds: this.currentTransactionIds,
480
- });
481
- // No concurrent exchange should happen at the same time, so all pending operations are part of the same exchange
482
- yield this.cancelPendingOperations();
483
- throw new TransportExchangeTimeoutError("Exchange aborted due to timeout");
484
- }
485
- tracer.withType("ble-error").trace(`Error while exchanging APDU`, { error });
486
- if (this.notYetDisconnected) {
487
- // In such case we will always disconnect because something is bad.
488
- // This sends a "disconnect" event
489
- yield _a.disconnectDevice(this.id);
490
- }
491
- const mappedError = remapError(error);
492
- tracer.trace("Error while exchanging APDU, mapped and throws following error", {
493
- mappedError,
494
- });
495
- throw mappedError;
496
- })), finalize(() => {
497
- tracer.trace("Clearing current transaction ids", {
498
- currentTransactionIds: this.currentTransactionIds,
499
- });
500
- this.clearCurrentTransactionIds();
501
- })));
502
- });
503
- };
504
- /**
505
- * Do not call this directly unless you know what you're doing. Communication
506
- * with a Ledger device should be through the {@link exchange} method.
507
- *
508
- * For each call a transaction id is added to the current stack of transaction ids.
509
- * With this transaction id, a pending BLE communication operations can be cancelled.
510
- * Note: each frame/packet of a longer BLE-encoded message to be sent should have their unique transaction id.
511
- *
512
- * @param buffer BLE-encoded packet to send to the device
513
- * @param frameId Frame id to make `write` aware of a bigger message that this frame/packet is part of.
514
- * Helps creating related a collection of transaction ids
515
- */
516
- this.write = (buffer) => __awaiter(this, void 0, void 0, function* () {
517
- const transactionId = uuid();
518
- this.currentTransactionIds.push(transactionId);
519
- const tracer = this.tracer.withUpdatedContext({ transactionId });
520
- tracer.trace("Writing to device", {
521
- willMessageBeAcked: !this.writableWithoutResponseCharacteristic,
522
- });
523
- try {
524
- if (!this.writableWithoutResponseCharacteristic) {
525
- // The message will be acked in response by the device
526
- yield this.writableWithResponseCharacteristic.writeWithResponse(buffer.toString("base64"), transactionId);
527
- }
528
- else {
529
- // The message won't be acked in response by the device
530
- yield this.writableWithoutResponseCharacteristic.writeWithoutResponse(buffer.toString("base64"), transactionId);
531
- }
532
- tracer.withType("ble-frame").trace("=> " + buffer.toString("hex"));
533
- }
534
- catch (error) {
535
- tracer.trace("Error while writing APDU", { error });
536
- throw new DisconnectedDeviceDuringOperation(error instanceof Error ? error.message : `${error}`);
537
- }
538
- });
539
489
  this.id = device.id;
540
490
  this.device = device;
541
491
  this.writableWithResponseCharacteristic = writableWithResponseCharacteristic;
@@ -547,6 +497,63 @@ class BleTransport extends Transport {
547
497
  clearDisconnectTimeout(this.id);
548
498
  this.tracer.trace(`New instance of BleTransport for device ${this.id}`);
549
499
  }
500
+ /**
501
+ * A message exchange (APDU request <-> response) with the device that can be aborted
502
+ *
503
+ * The message will be BLE-encoded/framed before being sent, and the response will be BLE-decoded.
504
+ *
505
+ * @param message A buffer (u8 array) of a none BLE-encoded message (an APDU for ex) to be sent to the device
506
+ * as a request
507
+ * @param options Contains optional options for the exchange function
508
+ * - abortTimeoutMs: stop the exchange after a given timeout. Another timeout exists
509
+ * to detect unresponsive device (see `unresponsiveTimeout`). This timeout aborts the exchange.
510
+ * @returns A promise that resolves with the response data from the device.
511
+ */
512
+ exchange = (message, { abortTimeoutMs } = {}) => {
513
+ const tracer = this.tracer.withUpdatedContext({
514
+ function: "exchange",
515
+ });
516
+ tracer.trace("Exchanging APDU ...", { abortTimeoutMs });
517
+ tracer.withType("apdu").trace(`=> ${message.toString("hex")}`);
518
+ return this.exchangeAtomicImpl(() => {
519
+ return firstValueFrom(
520
+ // `sendApdu` will only emit if an error occurred, otherwise it will complete,
521
+ // while `receiveAPDU` will emit the full response.
522
+ // Consequently it monitors the response while being able to reject on an error from the send.
523
+ merge(this.notifyObservable.pipe(data => receiveAPDU(data, { context: tracer.getContext() })), sendAPDU(this.write, message, this.mtuSize, {
524
+ context: tracer.getContext(),
525
+ })).pipe(abortTimeoutMs ? timeout(abortTimeoutMs, this.rxjsScheduler) : tap(), tap(data => {
526
+ tracer.withType("apdu").trace(`<= ${data.toString("hex")}`);
527
+ }), catchError(async (error) => {
528
+ // Currently only 1 reason the exchange has been explicitly aborted (other than job and transport errors): a timeout
529
+ if (error instanceof TimeoutError) {
530
+ tracer.trace("Aborting due to timeout and trying to cancel all communication write of the current exchange", {
531
+ abortTimeoutMs,
532
+ transactionIds: this.currentTransactionIds,
533
+ });
534
+ // No concurrent exchange should happen at the same time, so all pending operations are part of the same exchange
535
+ await this.cancelPendingOperations();
536
+ throw new TransportExchangeTimeoutError("Exchange aborted due to timeout");
537
+ }
538
+ tracer.withType("ble-error").trace(`Error while exchanging APDU`, { error });
539
+ if (this.notYetDisconnected) {
540
+ // In such case we will always disconnect because something is bad.
541
+ // This sends a "disconnect" event
542
+ await BleTransport.disconnectDevice(this.id);
543
+ }
544
+ const mappedError = remapError(error);
545
+ tracer.trace("Error while exchanging APDU, mapped and throws following error", {
546
+ mappedError,
547
+ });
548
+ throw mappedError;
549
+ }), finalize(() => {
550
+ tracer.trace("Clearing current transaction ids", {
551
+ currentTransactionIds: this.currentTransactionIds,
552
+ });
553
+ this.clearCurrentTransactionIds();
554
+ })));
555
+ });
556
+ };
550
557
  /**
551
558
  * Tries to cancel all operations that have a recorded transaction and are pending
552
559
  *
@@ -556,18 +563,16 @@ class BleTransport extends Transport {
556
563
  * but this error should be ignored. (In `exchange` our observable is unsubscribed before `cancelPendingOperations`
557
564
  * is called so the error is ignored)
558
565
  */
559
- cancelPendingOperations() {
560
- return __awaiter(this, void 0, void 0, function* () {
561
- for (const transactionId of this.currentTransactionIds) {
562
- try {
563
- this.tracer.trace("Cancelling operation", { transactionId });
564
- yield BlePlxManager.cancelTransaction(transactionId);
565
- }
566
- catch (error) {
567
- this.tracer.trace("Error while cancelling operation", { transactionId, error });
568
- }
566
+ async cancelPendingOperations() {
567
+ for (const transactionId of this.currentTransactionIds) {
568
+ try {
569
+ this.tracer.trace("Cancelling operation", { transactionId });
570
+ await BlePlxManager.cancelTransaction(transactionId);
569
571
  }
570
- });
572
+ catch (error) {
573
+ this.tracer.trace("Error while cancelling operation", { transactionId, error });
574
+ }
575
+ }
571
576
  }
572
577
  /**
573
578
  * Sets the collection of current transaction ids to an empty array
@@ -579,43 +584,41 @@ class BleTransport extends Transport {
579
584
  * Negotiate with the device the maximum transfer unit for the ble frames
580
585
  * @returns Promise<number>
581
586
  */
582
- inferMTU() {
583
- return __awaiter(this, void 0, void 0, function* () {
584
- let { mtu } = this.device;
585
- this.tracer.trace(`Inferring MTU ...`, { currentDeviceMtu: mtu });
586
- yield this.exchangeAtomicImpl(() => __awaiter(this, void 0, void 0, function* () {
587
- try {
588
- mtu = yield firstValueFrom(merge(this.notifyObservable.pipe(map(maybeError => {
589
- // Catches the PairingFailed Error that has only been emitted
590
- if (maybeError instanceof Error) {
591
- throw maybeError;
592
- }
593
- return maybeError;
594
- }), first(buffer => buffer.readUInt8(0) === 0x08), map(buffer => buffer.readUInt8(5))), defer(() => from(this.write(Buffer.from([0x08, 0, 0, 0, 0])))).pipe(ignoreElements())));
595
- }
596
- catch (error) {
597
- this.tracer.withType("ble-error").trace("Error while inferring MTU", { mtu });
598
- yield _a.disconnectDevice(this.id);
599
- const mappedError = remapError(error);
600
- this.tracer.trace("Error while inferring APDU, mapped and throws following error", {
601
- mappedError,
602
- });
603
- throw mappedError;
604
- }
605
- finally {
606
- // When negotiating the MTU, a message is sent/written to the device, and a transaction id was associated to this write
607
- this.clearCurrentTransactionIds();
608
- }
609
- }));
610
- this.tracer.trace(`Successfully negotiated MTU with device`, {
611
- mtu,
612
- mtuSize: this.mtuSize,
613
- });
614
- if (mtu > 20) {
615
- this.mtuSize = mtu;
587
+ async inferMTU() {
588
+ let { mtu } = this.device;
589
+ this.tracer.trace(`Inferring MTU ...`, { currentDeviceMtu: mtu });
590
+ await this.exchangeAtomicImpl(async () => {
591
+ try {
592
+ mtu = await firstValueFrom(merge(this.notifyObservable.pipe(map(maybeError => {
593
+ // Catches the PairingFailed Error that has only been emitted
594
+ if (maybeError instanceof Error) {
595
+ throw maybeError;
596
+ }
597
+ return maybeError;
598
+ }), first(buffer => buffer.readUInt8(0) === 0x08), map(buffer => buffer.readUInt8(5))), defer(() => from(this.write(Buffer.from([0x08, 0, 0, 0, 0])))).pipe(ignoreElements())));
616
599
  }
617
- return this.mtuSize;
600
+ catch (error) {
601
+ this.tracer.withType("ble-error").trace("Error while inferring MTU", { mtu });
602
+ await BleTransport.disconnectDevice(this.id);
603
+ const mappedError = remapError(error);
604
+ this.tracer.trace("Error while inferring APDU, mapped and throws following error", {
605
+ mappedError,
606
+ });
607
+ throw mappedError;
608
+ }
609
+ finally {
610
+ // When negotiating the MTU, a message is sent/written to the device, and a transaction id was associated to this write
611
+ this.clearCurrentTransactionIds();
612
+ }
613
+ });
614
+ this.tracer.trace(`Successfully negotiated MTU with device`, {
615
+ mtu,
616
+ mtuSize: this.mtuSize,
618
617
  });
618
+ if (mtu > 20) {
619
+ this.mtuSize = mtu;
620
+ }
621
+ return this.mtuSize;
619
622
  }
620
623
  /**
621
624
  * Exposed method from the ble-plx library
@@ -623,11 +626,44 @@ class BleTransport extends Transport {
623
626
  * @param {"Balanced" | "High" | "LowPower"} connectionPriority: Connection priority.
624
627
  * @returns {Promise<Device>} Connected device.
625
628
  */
626
- requestConnectionPriority(connectionPriority) {
627
- return __awaiter(this, void 0, void 0, function* () {
628
- return yield decoratePromiseErrors(this.device.requestConnectionPriority(ConnectionPriority[connectionPriority]));
629
- });
629
+ async requestConnectionPriority(connectionPriority) {
630
+ return await decoratePromiseErrors(this.device.requestConnectionPriority(ConnectionPriority[connectionPriority]));
630
631
  }
632
+ /**
633
+ * Do not call this directly unless you know what you're doing. Communication
634
+ * with a Ledger device should be through the {@link exchange} method.
635
+ *
636
+ * For each call a transaction id is added to the current stack of transaction ids.
637
+ * With this transaction id, a pending BLE communication operations can be cancelled.
638
+ * Note: each frame/packet of a longer BLE-encoded message to be sent should have their unique transaction id.
639
+ *
640
+ * @param buffer BLE-encoded packet to send to the device
641
+ * @param frameId Frame id to make `write` aware of a bigger message that this frame/packet is part of.
642
+ * Helps creating related a collection of transaction ids
643
+ */
644
+ write = async (buffer) => {
645
+ const transactionId = uuid();
646
+ this.currentTransactionIds.push(transactionId);
647
+ const tracer = this.tracer.withUpdatedContext({ transactionId });
648
+ tracer.trace("Writing to device", {
649
+ willMessageBeAcked: !this.writableWithoutResponseCharacteristic,
650
+ });
651
+ try {
652
+ if (!this.writableWithoutResponseCharacteristic) {
653
+ // The message will be acked in response by the device
654
+ await this.writableWithResponseCharacteristic.writeWithResponse(buffer.toString("base64"), transactionId);
655
+ }
656
+ else {
657
+ // The message won't be acked in response by the device
658
+ await this.writableWithoutResponseCharacteristic.writeWithoutResponse(buffer.toString("base64"), transactionId);
659
+ }
660
+ tracer.withType("ble-frame").trace("=> " + buffer.toString("hex"));
661
+ }
662
+ catch (error) {
663
+ tracer.trace("Error while writing APDU", { error });
664
+ throw new DisconnectedDeviceDuringOperation(error instanceof Error ? error.message : `${error}`);
665
+ }
666
+ };
631
667
  /**
632
668
  * We intentionally do not immediately close a transport connection.
633
669
  * Instead, we queue the disconnect and wait for a future connection to dismiss the event.
@@ -636,76 +672,29 @@ class BleTransport extends Transport {
636
672
  * already been disconnected.
637
673
  * @returns {Promise<void>}
638
674
  */
639
- close() {
640
- return __awaiter(this, void 0, void 0, function* () {
641
- const tracer = this.tracer.withUpdatedContext({ function: "close" });
642
- tracer.trace("Closing, queuing a disconnect with a timeout ...");
643
- let resolve;
644
- const disconnectPromise = new Promise(innerResolve => {
645
- resolve = innerResolve;
646
- });
647
- clearDisconnectTimeout(this.id);
648
- this.disconnectTimeout = setTimeout(() => {
649
- tracer.trace("Disconnect timeout has been reached ...");
650
- if (this.isConnected) {
651
- _a.disconnectDevice(this.id, tracer.getContext())
652
- .catch(() => { })
653
- .finally(resolve);
654
- }
655
- else {
656
- resolve();
657
- }
658
- }, _a.disconnectTimeoutMs);
659
- // The closure will occur no later than 5s, triggered either by disconnection
660
- // or the actual response of the apdu.
661
- yield Promise.race([this.exchangeBusyPromise || Promise.resolve(), disconnectPromise]);
662
- return;
675
+ async close() {
676
+ const tracer = this.tracer.withUpdatedContext({ function: "close" });
677
+ tracer.trace("Closing, queuing a disconnect with a timeout ...");
678
+ let resolve;
679
+ const disconnectPromise = new Promise(innerResolve => {
680
+ resolve = innerResolve;
663
681
  });
682
+ clearDisconnectTimeout(this.id);
683
+ this.disconnectTimeout = setTimeout(() => {
684
+ tracer.trace("Disconnect timeout has been reached ...");
685
+ if (this.isConnected) {
686
+ BleTransport.disconnectDevice(this.id, tracer.getContext())
687
+ .catch(() => { })
688
+ .finally(resolve);
689
+ }
690
+ else {
691
+ resolve();
692
+ }
693
+ }, BleTransport.disconnectTimeoutMs);
694
+ // The closure will occur no later than 5s, triggered either by disconnection
695
+ // or the actual response of the apdu.
696
+ await Promise.race([this.exchangeBusyPromise || Promise.resolve(), disconnectPromise]);
697
+ return;
664
698
  }
665
699
  }
666
- _a = BleTransport;
667
- BleTransport.disconnectTimeoutMs = 5000;
668
- /**
669
- *
670
- */
671
- BleTransport.isSupported = () => Promise.resolve(typeof BlePlxManager === "function");
672
- /**
673
- *
674
- */
675
- BleTransport.list = () => {
676
- throw new Error("not implemented");
677
- };
678
- // /**
679
- // * Exposed method from the ble-plx library
680
- // * Sets new log level for native module's logging mechanism.
681
- // * @param string logLevel New log level to be set.
682
- // */
683
- BleTransport.setLogLevel = BlePlxManager.setLogLevel;
684
- BleTransport.safeRemove = (sub, tracer) => {
685
- try {
686
- sub.remove();
687
- }
688
- catch (error) {
689
- tracer.trace("Error removing state subscription", { error });
690
- }
691
- };
692
- /**
693
- * Exposes method from the ble-plx library to disconnect a device
694
- *
695
- * Disconnects from {@link Device} if it's connected or cancels pending connection.
696
- * A "disconnect" event will normally be emitted by the ble-plx lib once the device is disconnected.
697
- * Errors are logged but silenced.
698
- */
699
- BleTransport.disconnectDevice = (id, context) => __awaiter(void 0, void 0, void 0, function* () {
700
- const tracer = new LocalTracer(LOG_TYPE, context);
701
- tracer.trace(`Trying to disconnect device ${id}`);
702
- yield BlePlxManager.disconnectDevice(id).catch(error => {
703
- // Only log, ignore if disconnect did not work
704
- tracer
705
- .withType("ble-error")
706
- .trace(`Error while trying to cancel device connection`, { error });
707
- });
708
- tracer.trace(`Device ${id} disconnected`);
709
- });
710
- export default BleTransport;
711
700
  //# sourceMappingURL=BleTransport.js.map