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