@ledgerhq/react-native-hw-transport-ble 6.29.5 → 6.30.0-nightly.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.
- package/CHANGELOG.md +16 -0
- package/lib/BleTransport.d.ts +19 -6
- package/lib/BleTransport.d.ts.map +1 -1
- package/lib/BleTransport.js +138 -87
- package/lib/BleTransport.js.map +1 -1
- package/lib/monitorCharacteristic.d.ts +2 -1
- package/lib/monitorCharacteristic.d.ts.map +1 -1
- package/lib/monitorCharacteristic.js +9 -5
- package/lib/monitorCharacteristic.js.map +1 -1
- package/lib-es/BleTransport.d.ts +19 -6
- package/lib-es/BleTransport.d.ts.map +1 -1
- package/lib-es/BleTransport.js +140 -89
- package/lib-es/BleTransport.js.map +1 -1
- package/lib-es/monitorCharacteristic.d.ts +2 -1
- package/lib-es/monitorCharacteristic.d.ts.map +1 -1
- package/lib-es/monitorCharacteristic.js +10 -6
- package/lib-es/monitorCharacteristic.js.map +1 -1
- package/package.json +5 -5
- package/src/BleTransport.ts +166 -86
- package/src/monitorCharacteristic.ts +17 -6
package/src/BleTransport.ts
CHANGED
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
getInfosForServiceUuid,
|
|
33
33
|
} from "@ledgerhq/devices";
|
|
34
34
|
import type { DeviceModel } from "@ledgerhq/devices";
|
|
35
|
-
import {
|
|
36
|
-
import { Observable, defer, merge, from, of, throwError, Observer } from "rxjs";
|
|
35
|
+
import { trace, LocalTracer, TraceContext } from "@ledgerhq/logs";
|
|
36
|
+
import { Observable, defer, merge, from, of, throwError, Observer, firstValueFrom } from "rxjs";
|
|
37
37
|
import { share, ignoreElements, first, map, tap, catchError } from "rxjs/operators";
|
|
38
38
|
import {
|
|
39
39
|
CantOpenDevice,
|
|
@@ -48,6 +48,8 @@ import { awaitsBleOn } from "./awaitsBleOn";
|
|
|
48
48
|
import { decoratePromiseErrors, remapError, mapBleErrorToHwTransportError } from "./remapErrors";
|
|
49
49
|
import { ReconnectionConfig } from "./types";
|
|
50
50
|
|
|
51
|
+
const LOG_TYPE = "ble-verbose";
|
|
52
|
+
|
|
51
53
|
/**
|
|
52
54
|
* This is potentially not needed anymore, to be checked if the bug is still
|
|
53
55
|
* happening.
|
|
@@ -108,31 +110,49 @@ const bleManagerInstance = (): BleManager => {
|
|
|
108
110
|
return _bleManager;
|
|
109
111
|
};
|
|
110
112
|
|
|
111
|
-
const clearDisconnectTimeout = (deviceId: string): void => {
|
|
113
|
+
const clearDisconnectTimeout = (deviceId: string, context?: TraceContext): void => {
|
|
112
114
|
const cachedTransport = transportsCache[deviceId];
|
|
113
115
|
if (cachedTransport && cachedTransport.disconnectTimeout) {
|
|
114
|
-
|
|
116
|
+
trace({ type: LOG_TYPE, message: "Clearing queued disconnect", context });
|
|
115
117
|
clearTimeout(cachedTransport.disconnectTimeout);
|
|
116
118
|
}
|
|
117
119
|
};
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Opens a BLE connection with a given device. Returns a Transport instance.
|
|
123
|
+
*
|
|
124
|
+
* @param deviceOrId
|
|
125
|
+
* @param needsReconnect
|
|
126
|
+
* @param timeoutMs TODO: to keep, used in a separate PR
|
|
127
|
+
* @param context Optional tracing/log context
|
|
128
|
+
* @returns A BleTransport instance
|
|
129
|
+
*/
|
|
130
|
+
async function open(
|
|
131
|
+
deviceOrId: Device | string,
|
|
132
|
+
needsReconnect: boolean,
|
|
133
|
+
timeoutMs?: number,
|
|
134
|
+
context?: TraceContext,
|
|
135
|
+
) {
|
|
136
|
+
const tracer = new LocalTracer(LOG_TYPE, context);
|
|
120
137
|
let device: Device;
|
|
121
|
-
|
|
138
|
+
tracer.trace(`Opening ${deviceOrId}`, { needsReconnect });
|
|
122
139
|
|
|
123
140
|
if (typeof deviceOrId === "string") {
|
|
124
141
|
if (transportsCache[deviceOrId]) {
|
|
125
|
-
|
|
142
|
+
tracer.trace(`Transport in cache, using it`);
|
|
126
143
|
clearDisconnectTimeout(deviceOrId);
|
|
144
|
+
|
|
145
|
+
// The cached transport probably has an older trace/log context
|
|
146
|
+
transportsCache[deviceOrId].setTraceContext(context);
|
|
127
147
|
return transportsCache[deviceOrId];
|
|
128
148
|
}
|
|
129
149
|
|
|
130
|
-
|
|
150
|
+
tracer.trace(`Trying to open device: ${deviceOrId}`);
|
|
131
151
|
await awaitsBleOn(bleManagerInstance());
|
|
132
152
|
|
|
133
153
|
// Returns a list of known devices by their identifiers
|
|
134
154
|
const devices = await bleManagerInstance().devices([deviceOrId]);
|
|
135
|
-
|
|
155
|
+
tracer.trace(`Found ${devices.length} already known device(s) with given id`, { deviceOrId });
|
|
136
156
|
[device] = devices;
|
|
137
157
|
|
|
138
158
|
if (!device) {
|
|
@@ -143,18 +163,25 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
143
163
|
getBluetoothServiceUuids(),
|
|
144
164
|
);
|
|
145
165
|
const connectedDevicesFiltered = connectedDevices.filter(d => d.id === deviceOrId);
|
|
146
|
-
|
|
166
|
+
tracer.trace(
|
|
167
|
+
`No known device with given id. Found ${connectedDevicesFiltered.length} devices from already connected devices`,
|
|
168
|
+
{ deviceOrId },
|
|
169
|
+
);
|
|
147
170
|
[device] = connectedDevicesFiltered;
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
if (!device) {
|
|
151
174
|
// We still don't have a device, so we attempt to connect to it.
|
|
152
|
-
|
|
175
|
+
tracer.trace(`No known nor connected devices with given id. Trying to connect to device`, {
|
|
176
|
+
deviceOrId,
|
|
177
|
+
timeoutMs,
|
|
178
|
+
});
|
|
179
|
+
|
|
153
180
|
// Nb ConnectionOptions dropped since it's not used internally by ble-plx.
|
|
154
181
|
try {
|
|
155
182
|
device = await bleManagerInstance().connectToDevice(deviceOrId, connectOptions);
|
|
156
183
|
} catch (e: any) {
|
|
157
|
-
|
|
184
|
+
tracer.trace(`Error code: ${e.errorCode}`);
|
|
158
185
|
if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
|
|
159
186
|
// If the MTU update did not work, we try to connect without requesting for a specific MTU
|
|
160
187
|
connectOptions = {};
|
|
@@ -174,19 +201,20 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
174
201
|
}
|
|
175
202
|
|
|
176
203
|
if (!(await device.isConnected())) {
|
|
177
|
-
|
|
204
|
+
tracer.trace(`Device found but not connected. connecting...`, { timeoutMs, connectOptions });
|
|
178
205
|
try {
|
|
179
|
-
await device.connect(connectOptions);
|
|
180
|
-
} catch (
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
206
|
+
await device.connect({ ...connectOptions });
|
|
207
|
+
} catch (error: any) {
|
|
208
|
+
tracer.trace(`Connect error`, { error });
|
|
209
|
+
if (error.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
|
|
210
|
+
tracer.trace(`Device mtu=${device.mtu}, reconnecting`);
|
|
184
211
|
connectOptions = {};
|
|
185
212
|
await device.connect();
|
|
186
|
-
} else if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
} else if (error.iosErrorCode === 14 || error.reason === "Peer removed pairing information") {
|
|
214
|
+
tracer.trace(`iOS broken pairing`, {
|
|
215
|
+
device,
|
|
216
|
+
bluetoothInfoCache: bluetoothInfoCache[device.id],
|
|
217
|
+
});
|
|
190
218
|
const { deviceModel } = bluetoothInfoCache[device.id] || {};
|
|
191
219
|
const { productName } = deviceModel || {};
|
|
192
220
|
throw new PeerRemovedPairing(undefined, {
|
|
@@ -194,12 +222,14 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
194
222
|
productName,
|
|
195
223
|
});
|
|
196
224
|
} else {
|
|
197
|
-
throw remapError(
|
|
225
|
+
throw remapError(error);
|
|
198
226
|
}
|
|
199
227
|
}
|
|
200
228
|
}
|
|
201
229
|
|
|
230
|
+
tracer.trace(`Device is connected now, getting services and characteristics`);
|
|
202
231
|
await device.discoverAllServicesAndCharacteristics();
|
|
232
|
+
|
|
203
233
|
let res: BluetoothInfos | undefined = retrieveInfos(device);
|
|
204
234
|
let characteristics: Characteristic[] | undefined;
|
|
205
235
|
|
|
@@ -216,6 +246,7 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
216
246
|
}
|
|
217
247
|
|
|
218
248
|
if (!res) {
|
|
249
|
+
tracer.trace(`Service not found`);
|
|
219
250
|
throw new TransportError("service not found", "BLEServiceNotFound");
|
|
220
251
|
}
|
|
221
252
|
|
|
@@ -226,6 +257,7 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
226
257
|
}
|
|
227
258
|
|
|
228
259
|
if (!characteristics) {
|
|
260
|
+
tracer.trace(`Characteristics not found`);
|
|
229
261
|
throw new TransportError("service not found", "BLEServiceNotFound");
|
|
230
262
|
}
|
|
231
263
|
|
|
@@ -271,27 +303,33 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
271
303
|
}
|
|
272
304
|
}
|
|
273
305
|
|
|
274
|
-
|
|
275
|
-
const notifyObservable = monitorCharacteristic(notifyC).pipe(
|
|
306
|
+
tracer.trace(`device.mtu=${device.mtu}`);
|
|
307
|
+
const notifyObservable = monitorCharacteristic(notifyC, context).pipe(
|
|
276
308
|
catchError(e => {
|
|
277
309
|
// LL-9033 fw 2.0.2 introduced this case, we silence the inner unhandled error.
|
|
278
310
|
const msg = String(e);
|
|
279
|
-
return msg.includes("notify change failed")
|
|
311
|
+
return msg.includes("notify change failed")
|
|
312
|
+
? of(new PairingFailed(msg))
|
|
313
|
+
: throwError(() => e);
|
|
280
314
|
}),
|
|
281
315
|
tap(value => {
|
|
282
316
|
if (value instanceof PairingFailed) return;
|
|
283
|
-
|
|
317
|
+
trace({ type: "ble-frame", message: `<= ${value.toString("hex")}`, context });
|
|
284
318
|
}),
|
|
285
319
|
share(),
|
|
286
320
|
);
|
|
287
321
|
const notif = notifyObservable.subscribe();
|
|
288
|
-
|
|
322
|
+
|
|
323
|
+
const transport = new BleTransport(device, writeC, writeCmdC, notifyObservable, deviceModel, {
|
|
324
|
+
context,
|
|
325
|
+
});
|
|
326
|
+
tracer.trace(`New BleTransport created`);
|
|
289
327
|
|
|
290
328
|
// Keeping it as a comment for now but if no new bluetooth issues occur, we will be able to remove it
|
|
291
329
|
// await transport.requestConnectionPriority("High");
|
|
292
330
|
// eslint-disable-next-line prefer-const
|
|
293
331
|
let disconnectedSub: Subscription;
|
|
294
|
-
const onDisconnect = (
|
|
332
|
+
const onDisconnect = (error: BleError | null) => {
|
|
295
333
|
transport.isConnected = false;
|
|
296
334
|
transport.notYetDisconnected = false;
|
|
297
335
|
notif.unsubscribe();
|
|
@@ -299,8 +337,11 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
299
337
|
|
|
300
338
|
clearDisconnectTimeout(transport.id);
|
|
301
339
|
delete transportsCache[transport.id];
|
|
302
|
-
|
|
303
|
-
|
|
340
|
+
tracer.trace(
|
|
341
|
+
`On device disconnected callback: cleared cached transport for ${transport.id}, emitting Transport event "disconnect"`,
|
|
342
|
+
{ reason: error },
|
|
343
|
+
);
|
|
344
|
+
transport.emit("disconnect", error);
|
|
304
345
|
};
|
|
305
346
|
|
|
306
347
|
// eslint-disable-next-line require-atomic-updates
|
|
@@ -325,7 +366,7 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
325
366
|
if (afterMTUTime - beforeMTUTime < reconnectionConfig.pairingThreshold) {
|
|
326
367
|
needsReconnect = false;
|
|
327
368
|
} else if (deviceModel.id === DeviceModelId.stax) {
|
|
328
|
-
|
|
369
|
+
tracer.trace(`Skipping "needsReconnect" strategy for Stax`);
|
|
329
370
|
needsReconnect = false;
|
|
330
371
|
}
|
|
331
372
|
|
|
@@ -339,8 +380,8 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
339
380
|
}
|
|
340
381
|
|
|
341
382
|
if (needsReconnect) {
|
|
342
|
-
|
|
343
|
-
return open(device, false);
|
|
383
|
+
tracer.trace(`Reconnecting`);
|
|
384
|
+
return open(device, false, timeoutMs, context);
|
|
344
385
|
}
|
|
345
386
|
|
|
346
387
|
return transport;
|
|
@@ -351,7 +392,6 @@ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
|
|
|
351
392
|
* @example
|
|
352
393
|
* import BleTransport from "@ledgerhq/react-native-hw-transport-ble";
|
|
353
394
|
*/
|
|
354
|
-
const TAG = "ble-verbose";
|
|
355
395
|
export default class BleTransport extends Transport {
|
|
356
396
|
static disconnectTimeoutMs = 5000;
|
|
357
397
|
/**
|
|
@@ -410,8 +450,12 @@ export default class BleTransport extends Transport {
|
|
|
410
450
|
* @param observer Device is partial in order to avoid the live-common/this dep
|
|
411
451
|
* @returns TransportSubscription
|
|
412
452
|
*/
|
|
413
|
-
static listen(
|
|
414
|
-
|
|
453
|
+
static listen(
|
|
454
|
+
observer: TransportObserver<any, HwTransportError>,
|
|
455
|
+
context?: TraceContext,
|
|
456
|
+
): TransportSubscription {
|
|
457
|
+
const tracer = new LocalTracer(LOG_TYPE, context);
|
|
458
|
+
tracer.trace("Listening for devices ...");
|
|
415
459
|
|
|
416
460
|
let unsubscribed: boolean;
|
|
417
461
|
|
|
@@ -421,7 +465,7 @@ export default class BleTransport extends Transport {
|
|
|
421
465
|
const devices = await bleManagerInstance().connectedDevices(getBluetoothServiceUuids());
|
|
422
466
|
if (unsubscribed) return;
|
|
423
467
|
if (devices.length) {
|
|
424
|
-
|
|
468
|
+
tracer.trace("Disconnecting from all devices", { deviceCount: devices.length });
|
|
425
469
|
|
|
426
470
|
await Promise.all(devices.map(d => BleTransport.disconnect(d.id).catch(() => {})));
|
|
427
471
|
}
|
|
@@ -457,7 +501,7 @@ export default class BleTransport extends Transport {
|
|
|
457
501
|
bleManagerInstance().stopDeviceScan();
|
|
458
502
|
stateSub.remove();
|
|
459
503
|
|
|
460
|
-
|
|
504
|
+
tracer.trace("Done listening");
|
|
461
505
|
};
|
|
462
506
|
|
|
463
507
|
return {
|
|
@@ -466,21 +510,37 @@ export default class BleTransport extends Transport {
|
|
|
466
510
|
}
|
|
467
511
|
|
|
468
512
|
/**
|
|
469
|
-
*
|
|
513
|
+
* Opens a BLE transport
|
|
514
|
+
*
|
|
470
515
|
* @param {Device | string} deviceOrId
|
|
516
|
+
* @param timeoutMs TODO: to keep, used in a separate PR
|
|
517
|
+
* @param context An optional context object for log/tracing strategy
|
|
471
518
|
*/
|
|
472
|
-
static async open(
|
|
473
|
-
|
|
519
|
+
static async open(
|
|
520
|
+
deviceOrId: Device | string,
|
|
521
|
+
timeoutMs?: number,
|
|
522
|
+
context?: TraceContext,
|
|
523
|
+
): Promise<BleTransport> {
|
|
524
|
+
return open(deviceOrId, true, timeoutMs, context);
|
|
474
525
|
}
|
|
475
526
|
|
|
476
527
|
/**
|
|
477
|
-
*
|
|
528
|
+
* Exposes method from the ble-plx library to disconnect a device
|
|
529
|
+
*
|
|
478
530
|
* Disconnects from {@link Device} if it's connected or cancels pending connection.
|
|
479
531
|
*/
|
|
480
|
-
static disconnect = async (id: DeviceId): Promise<void> => {
|
|
481
|
-
|
|
532
|
+
static disconnect = async (id: DeviceId, context?: TraceContext): Promise<void> => {
|
|
533
|
+
trace({
|
|
534
|
+
type: LOG_TYPE,
|
|
535
|
+
message: `Trying to disconnect device ${id})`,
|
|
536
|
+
context,
|
|
537
|
+
});
|
|
482
538
|
await bleManagerInstance().cancelDeviceConnection(id);
|
|
483
|
-
|
|
539
|
+
trace({
|
|
540
|
+
type: LOG_TYPE,
|
|
541
|
+
message: `Device ${id} disconnected`,
|
|
542
|
+
context,
|
|
543
|
+
});
|
|
484
544
|
};
|
|
485
545
|
|
|
486
546
|
device: Device;
|
|
@@ -500,8 +560,9 @@ export default class BleTransport extends Transport {
|
|
|
500
560
|
writeCmdCharacteristic: Characteristic | undefined,
|
|
501
561
|
notifyObservable: Observable<any>,
|
|
502
562
|
deviceModel: DeviceModel,
|
|
563
|
+
{ context }: { context?: TraceContext } = {},
|
|
503
564
|
) {
|
|
504
|
-
super();
|
|
565
|
+
super({ context, logType: LOG_TYPE });
|
|
505
566
|
this.id = device.id;
|
|
506
567
|
this.device = device;
|
|
507
568
|
this.writeCharacteristic = writeCharacteristic;
|
|
@@ -509,33 +570,39 @@ export default class BleTransport extends Transport {
|
|
|
509
570
|
this.notifyObservable = notifyObservable;
|
|
510
571
|
this.deviceModel = deviceModel;
|
|
511
572
|
|
|
512
|
-
log(TAG, `BleTransport(${String(this.id)}) new instance`);
|
|
513
573
|
clearDisconnectTimeout(this.id);
|
|
574
|
+
|
|
575
|
+
this.tracer.trace(`New instance of BleTransport for device ${this.id}`);
|
|
514
576
|
}
|
|
515
577
|
|
|
516
578
|
/**
|
|
517
579
|
* Send data to the device using a low level API.
|
|
518
580
|
* It's recommended to use the "send" method for a higher level API.
|
|
581
|
+
*
|
|
519
582
|
* @param {Buffer} apdu - The data to send.
|
|
520
583
|
* @returns {Promise<Buffer>} A promise that resolves with the response data from the device.
|
|
521
584
|
*/
|
|
522
|
-
exchange = (apdu: Buffer): Promise<any> =>
|
|
523
|
-
this.
|
|
585
|
+
exchange = (apdu: Buffer): Promise<any> => {
|
|
586
|
+
const tracer = this.tracer.withUpdatedContext({
|
|
587
|
+
function: "exchange",
|
|
588
|
+
});
|
|
589
|
+
tracer.trace("Exchanging APDU ...");
|
|
590
|
+
|
|
591
|
+
return this.exchangeAtomicImpl(async () => {
|
|
524
592
|
try {
|
|
525
593
|
const msgIn = apdu.toString("hex");
|
|
526
|
-
|
|
594
|
+
tracer.withType("apdu").trace(`=> ${msgIn}`);
|
|
527
595
|
|
|
528
|
-
const data = await
|
|
529
|
-
this.notifyObservable.pipe(receiveAPDU),
|
|
530
|
-
|
|
531
|
-
).toPromise();
|
|
596
|
+
const data = await firstValueFrom(
|
|
597
|
+
merge(this.notifyObservable.pipe(receiveAPDU), sendAPDU(this.write, apdu, this.mtuSize)),
|
|
598
|
+
);
|
|
532
599
|
|
|
533
600
|
const msgOut = data.toString("hex");
|
|
534
|
-
|
|
601
|
+
tracer.withType("apdu").trace(`<= ${msgOut}`);
|
|
535
602
|
|
|
536
603
|
return data;
|
|
537
|
-
} catch (
|
|
538
|
-
|
|
604
|
+
} catch (error: any) {
|
|
605
|
+
tracer.withType("ble-error").trace(`Error while exchanging APDU`, { error });
|
|
539
606
|
|
|
540
607
|
if (this.notYetDisconnected) {
|
|
541
608
|
// in such case we will always disconnect because something is bad.
|
|
@@ -544,9 +611,14 @@ export default class BleTransport extends Transport {
|
|
|
544
611
|
.catch(() => {}); // but we ignore if disconnect worked.
|
|
545
612
|
}
|
|
546
613
|
|
|
547
|
-
|
|
614
|
+
const mappedError = remapError(error);
|
|
615
|
+
tracer.trace("Error while exchanging APDU, mapped and throws following error", {
|
|
616
|
+
mappedError,
|
|
617
|
+
});
|
|
618
|
+
throw mappedError;
|
|
548
619
|
}
|
|
549
620
|
});
|
|
621
|
+
};
|
|
550
622
|
|
|
551
623
|
/**
|
|
552
624
|
* Negotiate with the device the maximum transfer unit for the ble frames
|
|
@@ -554,33 +626,43 @@ export default class BleTransport extends Transport {
|
|
|
554
626
|
*/
|
|
555
627
|
async inferMTU(): Promise<number> {
|
|
556
628
|
let { mtu } = this.device;
|
|
629
|
+
this.tracer.trace(`Inferring MTU ...`, { currentDeviceMtu: mtu });
|
|
557
630
|
|
|
558
631
|
await this.exchangeAtomicImpl(async () => {
|
|
559
632
|
try {
|
|
560
|
-
mtu = await
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
633
|
+
mtu = await firstValueFrom(
|
|
634
|
+
merge(
|
|
635
|
+
this.notifyObservable.pipe(
|
|
636
|
+
tap(maybeError => {
|
|
637
|
+
if (maybeError instanceof Error) throw maybeError;
|
|
638
|
+
}),
|
|
639
|
+
first(buffer => buffer.readUInt8(0) === 0x08),
|
|
640
|
+
map(buffer => buffer.readUInt8(5)),
|
|
641
|
+
),
|
|
642
|
+
defer(() => from(this.write(Buffer.from([0x08, 0, 0, 0, 0])))).pipe(ignoreElements()),
|
|
567
643
|
),
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
log("ble-error", "inferMTU got " + JSON.stringify(e));
|
|
644
|
+
);
|
|
645
|
+
} catch (error: any) {
|
|
646
|
+
this.tracer.withType("ble-error").trace("Error while inferring MTU", { mtu });
|
|
572
647
|
|
|
573
648
|
await bleManagerInstance()
|
|
574
649
|
.cancelDeviceConnection(this.id)
|
|
575
650
|
.catch(() => {}); // but we ignore if disconnect worked.
|
|
576
651
|
|
|
577
|
-
|
|
652
|
+
const mappedError = remapError(error);
|
|
653
|
+
this.tracer.trace("Error while inferring APDU, mapped and throws following error", {
|
|
654
|
+
mappedError,
|
|
655
|
+
});
|
|
656
|
+
throw mappedError;
|
|
578
657
|
}
|
|
579
658
|
});
|
|
580
659
|
|
|
660
|
+
this.tracer.trace(`Successfully negotiated MTU with device`, {
|
|
661
|
+
mtu,
|
|
662
|
+
mtuSize: this.mtuSize,
|
|
663
|
+
});
|
|
581
664
|
if (mtu > 20) {
|
|
582
665
|
this.mtuSize = mtu;
|
|
583
|
-
log(TAG, `BleTransport(${this.id}) mtu set to ${this.mtuSize}`);
|
|
584
666
|
}
|
|
585
667
|
|
|
586
668
|
return this.mtuSize;
|
|
@@ -607,19 +689,18 @@ export default class BleTransport extends Transport {
|
|
|
607
689
|
* @param txid
|
|
608
690
|
*/
|
|
609
691
|
write = async (buffer: Buffer, txid?: string | undefined): Promise<void> => {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
try {
|
|
692
|
+
try {
|
|
693
|
+
if (!this.writeCmdCharacteristic) {
|
|
613
694
|
await this.writeCharacteristic.writeWithResponse(buffer.toString("base64"), txid);
|
|
614
|
-
}
|
|
615
|
-
throw new DisconnectedDeviceDuringOperation(e.message);
|
|
616
|
-
}
|
|
617
|
-
} else {
|
|
618
|
-
try {
|
|
695
|
+
} else {
|
|
619
696
|
await this.writeCmdCharacteristic.writeWithoutResponse(buffer.toString("base64"), txid);
|
|
620
|
-
} catch (e: any) {
|
|
621
|
-
throw new DisconnectedDeviceDuringOperation(e.message);
|
|
622
697
|
}
|
|
698
|
+
this.tracer.withType("ble-frame").trace("=> " + buffer.toString("hex"));
|
|
699
|
+
} catch (error: unknown) {
|
|
700
|
+
this.tracer.trace("Error while writing APDU", { error });
|
|
701
|
+
throw new DisconnectedDeviceDuringOperation(
|
|
702
|
+
error instanceof Error ? error.message : `${error}`,
|
|
703
|
+
);
|
|
623
704
|
}
|
|
624
705
|
};
|
|
625
706
|
|
|
@@ -632,6 +713,8 @@ export default class BleTransport extends Transport {
|
|
|
632
713
|
* @returns {Promise<void>}
|
|
633
714
|
*/
|
|
634
715
|
async close(): Promise<void> {
|
|
716
|
+
this.tracer.trace("Closing, queuing a disconnect ...");
|
|
717
|
+
|
|
635
718
|
let resolve: (value: void | PromiseLike<void>) => void;
|
|
636
719
|
const disconnectPromise = new Promise<void>(innerResolve => {
|
|
637
720
|
resolve = innerResolve;
|
|
@@ -639,12 +722,9 @@ export default class BleTransport extends Transport {
|
|
|
639
722
|
|
|
640
723
|
clearDisconnectTimeout(this.id);
|
|
641
724
|
|
|
642
|
-
log(TAG, "Queuing a disconnect");
|
|
643
|
-
|
|
644
725
|
this.disconnectTimeout = setTimeout(() => {
|
|
645
|
-
log(TAG, `Triggering a disconnect from ${this.id}`);
|
|
646
726
|
if (this.isConnected) {
|
|
647
|
-
BleTransport.disconnect(this.id)
|
|
727
|
+
BleTransport.disconnect(this.id, this.tracer.getContext())
|
|
648
728
|
.catch(() => {})
|
|
649
729
|
.finally(resolve);
|
|
650
730
|
} else {
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
2
|
import { TransportError } from "@ledgerhq/errors";
|
|
3
3
|
import type { Characteristic } from "./types";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { LocalTracer, TraceContext } from "@ledgerhq/logs";
|
|
5
|
+
|
|
6
|
+
const LOG_TYPE = "ble-verbose";
|
|
7
|
+
|
|
8
|
+
export const monitorCharacteristic = (
|
|
9
|
+
characteristic: Characteristic,
|
|
10
|
+
context?: TraceContext,
|
|
11
|
+
): Observable<Buffer> =>
|
|
6
12
|
new Observable(o => {
|
|
7
|
-
|
|
13
|
+
const tracer = new LocalTracer(LOG_TYPE, context);
|
|
14
|
+
tracer.trace(`Start monitoring BLE characteristics`, {
|
|
15
|
+
characteristicUuid: characteristic.uuid,
|
|
16
|
+
});
|
|
17
|
+
|
|
8
18
|
const subscription = characteristic.monitor((error, c) => {
|
|
9
19
|
if (error) {
|
|
10
|
-
|
|
20
|
+
tracer.trace("Error while monitoring characteristics", { error });
|
|
11
21
|
o.error(error);
|
|
12
22
|
} else if (!c) {
|
|
23
|
+
tracer.trace("BLE monitored characteristic null value");
|
|
13
24
|
o.error(
|
|
14
|
-
new TransportError("
|
|
25
|
+
new TransportError("Characteristic monitor null value", "CharacteristicMonitorNull"),
|
|
15
26
|
);
|
|
16
27
|
} else {
|
|
17
28
|
try {
|
|
@@ -22,8 +33,8 @@ export const monitorCharacteristic = (characteristic: Characteristic): Observabl
|
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
});
|
|
36
|
+
|
|
25
37
|
return () => {
|
|
26
|
-
log("ble-verbose", "end monitor " + characteristic.uuid);
|
|
27
38
|
subscription.remove();
|
|
28
39
|
};
|
|
29
40
|
});
|