@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +9 -0
- package/lib/BlePlxManager.js +27 -40
- package/lib/BlePlxManager.js.map +1 -1
- package/lib/BleTransport.d.ts +1 -0
- package/lib/BleTransport.d.ts.map +1 -1
- package/lib/BleTransport.js +432 -442
- package/lib/BleTransport.js.map +1 -1
- package/lib/BleTransport.test.js +91 -104
- package/lib/BleTransport.test.js.map +1 -1
- package/lib/monitorCharacteristic.d.ts +1 -0
- package/lib/monitorCharacteristic.d.ts.map +1 -1
- package/lib/remapErrors.js +1 -1
- package/lib/remapErrors.js.map +1 -1
- package/lib-es/BlePlxManager.js +27 -40
- package/lib-es/BlePlxManager.js.map +1 -1
- package/lib-es/BleTransport.d.ts +1 -0
- package/lib-es/BleTransport.d.ts.map +1 -1
- package/lib-es/BleTransport.js +433 -444
- package/lib-es/BleTransport.js.map +1 -1
- package/lib-es/BleTransport.test.js +91 -104
- package/lib-es/BleTransport.test.js.map +1 -1
- package/lib-es/monitorCharacteristic.d.ts +1 -0
- package/lib-es/monitorCharacteristic.d.ts.map +1 -1
- package/lib-es/remapErrors.js +1 -1
- package/lib-es/remapErrors.js.map +1 -1
- package/package.json +8 -8
package/lib-es/BleTransport.js
CHANGED
|
@@ -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(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 (!
|
|
149
|
-
|
|
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
|
-
|
|
115
|
+
device = await BlePlxManager.connect(deviceOrId, {
|
|
116
|
+
...connectOptions,
|
|
117
|
+
timeout: timeoutMs,
|
|
118
|
+
});
|
|
152
119
|
}
|
|
153
|
-
catch (
|
|
154
|
-
tracer.trace(`
|
|
155
|
-
if (
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
128
|
+
throw e;
|
|
181
129
|
}
|
|
182
130
|
}
|
|
183
131
|
}
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
|
224
|
-
|
|
171
|
+
else {
|
|
172
|
+
throw remapError(error);
|
|
225
173
|
}
|
|
226
174
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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 (
|
|
237
|
-
|
|
212
|
+
else if (c.uuid === writeCmdUuid) {
|
|
213
|
+
writableWithoutResponseCharacteristic = c;
|
|
238
214
|
}
|
|
239
|
-
if (
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
return open(device, false, timeoutMs, context);
|
|
306
|
+
else {
|
|
307
|
+
needsReconnect = false;
|
|
321
308
|
}
|
|
322
|
-
|
|
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) =>
|
|
375
|
+
const stateSub = BlePlxManager.onStateChange(async (state) => {
|
|
360
376
|
if (state === "PoweredOn") {
|
|
361
|
-
|
|
362
|
-
const devices =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
414
|
+
}, true);
|
|
399
415
|
const unsubscribe = () => {
|
|
400
416
|
if (unsubscribed)
|
|
401
417
|
return;
|
|
402
418
|
unsubscribed = true;
|
|
403
419
|
BlePlxManager.stopScan().then(() => {
|
|
404
|
-
|
|
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(
|
|
422
|
-
return
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|