@ledgerhq/react-native-hw-transport-ble 6.24.1 → 6.25.1-alpha.3

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.
@@ -0,0 +1,60 @@
1
+ // flow-typed signature: 26e7ad1e6da0616d6c840ee4a1331894
2
+ // flow-typed version: <<STUB>>/@ledgerhq/devices_v^4.68.0/flow_v0.102.0
3
+
4
+ /**
5
+ * This is an autogenerated libdef stub for:
6
+ *
7
+ * '@ledgerhq/devices'
8
+ *
9
+ * Fill this stub out by replacing all the `any` types.
10
+ *
11
+ * Once filled out, we encourage you to share your work with the
12
+ * community by sending a pull request to:
13
+ * https://github.com/flowtype/flow-typed
14
+ */
15
+
16
+ declare module '@ledgerhq/devices' {
17
+ declare module.exports: any;
18
+ }
19
+
20
+ /**
21
+ * We include stubs for each file inside this npm package in case you need to
22
+ * require those files directly. Feel free to delete any files that aren't
23
+ * needed.
24
+ */
25
+ declare module '@ledgerhq/devices/src/ble/receiveAPDU' {
26
+ declare module.exports: any;
27
+ }
28
+
29
+ declare module '@ledgerhq/devices/src/ble/sendAPDU' {
30
+ declare module.exports: any;
31
+ }
32
+
33
+ declare module '@ledgerhq/devices/src/hid-framing' {
34
+ declare module.exports: any;
35
+ }
36
+
37
+ declare module '@ledgerhq/devices/src/index' {
38
+ declare module.exports: any;
39
+ }
40
+
41
+ declare module '@ledgerhq/devices/src/scrambling' {
42
+ declare module.exports: any;
43
+ }
44
+
45
+ // Filename aliases
46
+ declare module '@ledgerhq/devices/src/ble/receiveAPDU.js' {
47
+ declare module.exports: $Exports<'@ledgerhq/devices/src/ble/receiveAPDU'>;
48
+ }
49
+ declare module '@ledgerhq/devices/src/ble/sendAPDU.js' {
50
+ declare module.exports: $Exports<'@ledgerhq/devices/src/ble/sendAPDU'>;
51
+ }
52
+ declare module '@ledgerhq/devices/src/hid-framing.js' {
53
+ declare module.exports: $Exports<'@ledgerhq/devices/src/hid-framing'>;
54
+ }
55
+ declare module '@ledgerhq/devices/src/index.js' {
56
+ declare module.exports: $Exports<'@ledgerhq/devices/src/index'>;
57
+ }
58
+ declare module '@ledgerhq/devices/src/scrambling.js' {
59
+ declare module.exports: $Exports<'@ledgerhq/devices/src/scrambling'>;
60
+ }
@@ -0,0 +1,186 @@
1
+ // flow-typed signature: 89c66982abff5d29344111606d02789b
2
+ // flow-typed version: <<STUB>>/events_v^3.0.0/flow_v0.102.0
3
+
4
+ /**
5
+ * This is an autogenerated libdef stub for:
6
+ *
7
+ * 'events'
8
+ *
9
+ * Fill this stub out by replacing all the `any` types.
10
+ *
11
+ * Once filled out, we encourage you to share your work with the
12
+ * community by sending a pull request to:
13
+ * https://github.com/flowtype/flow-typed
14
+ */
15
+
16
+ declare module 'events' {
17
+ declare module.exports: any;
18
+ }
19
+
20
+ /**
21
+ * We include stubs for each file inside this npm package in case you need to
22
+ * require those files directly. Feel free to delete any files that aren't
23
+ * needed.
24
+ */
25
+ declare module 'events/events' {
26
+ declare module.exports: any;
27
+ }
28
+
29
+ declare module 'events/tests/add-listeners' {
30
+ declare module.exports: any;
31
+ }
32
+
33
+ declare module 'events/tests/check-listener-leaks' {
34
+ declare module.exports: any;
35
+ }
36
+
37
+ declare module 'events/tests/common' {
38
+ declare module.exports: any;
39
+ }
40
+
41
+ declare module 'events/tests/errors' {
42
+ declare module.exports: any;
43
+ }
44
+
45
+ declare module 'events/tests/events-list' {
46
+ declare module.exports: any;
47
+ }
48
+
49
+ declare module 'events/tests/index' {
50
+ declare module.exports: any;
51
+ }
52
+
53
+ declare module 'events/tests/legacy-compat' {
54
+ declare module.exports: any;
55
+ }
56
+
57
+ declare module 'events/tests/listener-count' {
58
+ declare module.exports: any;
59
+ }
60
+
61
+ declare module 'events/tests/listeners-side-effects' {
62
+ declare module.exports: any;
63
+ }
64
+
65
+ declare module 'events/tests/listeners' {
66
+ declare module.exports: any;
67
+ }
68
+
69
+ declare module 'events/tests/max-listeners' {
70
+ declare module.exports: any;
71
+ }
72
+
73
+ declare module 'events/tests/method-names' {
74
+ declare module.exports: any;
75
+ }
76
+
77
+ declare module 'events/tests/modify-in-emit' {
78
+ declare module.exports: any;
79
+ }
80
+
81
+ declare module 'events/tests/num-args' {
82
+ declare module.exports: any;
83
+ }
84
+
85
+ declare module 'events/tests/once' {
86
+ declare module.exports: any;
87
+ }
88
+
89
+ declare module 'events/tests/prepend' {
90
+ declare module.exports: any;
91
+ }
92
+
93
+ declare module 'events/tests/remove-all-listeners' {
94
+ declare module.exports: any;
95
+ }
96
+
97
+ declare module 'events/tests/remove-listeners' {
98
+ declare module.exports: any;
99
+ }
100
+
101
+ declare module 'events/tests/set-max-listeners-side-effects' {
102
+ declare module.exports: any;
103
+ }
104
+
105
+ declare module 'events/tests/special-event-names' {
106
+ declare module.exports: any;
107
+ }
108
+
109
+ declare module 'events/tests/subclass' {
110
+ declare module.exports: any;
111
+ }
112
+
113
+ declare module 'events/tests/symbols' {
114
+ declare module.exports: any;
115
+ }
116
+
117
+ // Filename aliases
118
+ declare module 'events/events.js' {
119
+ declare module.exports: $Exports<'events/events'>;
120
+ }
121
+ declare module 'events/tests/add-listeners.js' {
122
+ declare module.exports: $Exports<'events/tests/add-listeners'>;
123
+ }
124
+ declare module 'events/tests/check-listener-leaks.js' {
125
+ declare module.exports: $Exports<'events/tests/check-listener-leaks'>;
126
+ }
127
+ declare module 'events/tests/common.js' {
128
+ declare module.exports: $Exports<'events/tests/common'>;
129
+ }
130
+ declare module 'events/tests/errors.js' {
131
+ declare module.exports: $Exports<'events/tests/errors'>;
132
+ }
133
+ declare module 'events/tests/events-list.js' {
134
+ declare module.exports: $Exports<'events/tests/events-list'>;
135
+ }
136
+ declare module 'events/tests/index.js' {
137
+ declare module.exports: $Exports<'events/tests/index'>;
138
+ }
139
+ declare module 'events/tests/legacy-compat.js' {
140
+ declare module.exports: $Exports<'events/tests/legacy-compat'>;
141
+ }
142
+ declare module 'events/tests/listener-count.js' {
143
+ declare module.exports: $Exports<'events/tests/listener-count'>;
144
+ }
145
+ declare module 'events/tests/listeners-side-effects.js' {
146
+ declare module.exports: $Exports<'events/tests/listeners-side-effects'>;
147
+ }
148
+ declare module 'events/tests/listeners.js' {
149
+ declare module.exports: $Exports<'events/tests/listeners'>;
150
+ }
151
+ declare module 'events/tests/max-listeners.js' {
152
+ declare module.exports: $Exports<'events/tests/max-listeners'>;
153
+ }
154
+ declare module 'events/tests/method-names.js' {
155
+ declare module.exports: $Exports<'events/tests/method-names'>;
156
+ }
157
+ declare module 'events/tests/modify-in-emit.js' {
158
+ declare module.exports: $Exports<'events/tests/modify-in-emit'>;
159
+ }
160
+ declare module 'events/tests/num-args.js' {
161
+ declare module.exports: $Exports<'events/tests/num-args'>;
162
+ }
163
+ declare module 'events/tests/once.js' {
164
+ declare module.exports: $Exports<'events/tests/once'>;
165
+ }
166
+ declare module 'events/tests/prepend.js' {
167
+ declare module.exports: $Exports<'events/tests/prepend'>;
168
+ }
169
+ declare module 'events/tests/remove-all-listeners.js' {
170
+ declare module.exports: $Exports<'events/tests/remove-all-listeners'>;
171
+ }
172
+ declare module 'events/tests/remove-listeners.js' {
173
+ declare module.exports: $Exports<'events/tests/remove-listeners'>;
174
+ }
175
+ declare module 'events/tests/set-max-listeners-side-effects.js' {
176
+ declare module.exports: $Exports<'events/tests/set-max-listeners-side-effects'>;
177
+ }
178
+ declare module 'events/tests/special-event-names.js' {
179
+ declare module.exports: $Exports<'events/tests/special-event-names'>;
180
+ }
181
+ declare module 'events/tests/subclass.js' {
182
+ declare module.exports: $Exports<'events/tests/subclass'>;
183
+ }
184
+ declare module 'events/tests/symbols.js' {
185
+ declare module.exports: $Exports<'events/tests/symbols'>;
186
+ }
@@ -0,0 +1,6 @@
1
+ // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583
2
+ // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x
3
+
4
+ declare module "flow-bin" {
5
+ declare module.exports: string;
6
+ }
@@ -0,0 +1,193 @@
1
+ // flow-typed signature: e8d7dbb588d418c516e0db0aa0f6becd
2
+ // flow-typed version: <<STUB>>/flow-typed_v^2.4.0/flow_v0.68.0
3
+
4
+ /**
5
+ * This is an autogenerated libdef stub for:
6
+ *
7
+ * 'flow-typed'
8
+ *
9
+ * Fill this stub out by replacing all the `any` types.
10
+ *
11
+ * Once filled out, we encourage you to share your work with the
12
+ * community by sending a pull request to:
13
+ * https://github.com/flowtype/flow-typed
14
+ */
15
+
16
+ declare module 'flow-typed' {
17
+ declare module.exports: any;
18
+ }
19
+
20
+ /**
21
+ * We include stubs for each file inside this npm package in case you need to
22
+ * require those files directly. Feel free to delete any files that aren't
23
+ * needed.
24
+ */
25
+ declare module 'flow-typed/dist/cli' {
26
+ declare module.exports: any;
27
+ }
28
+
29
+ declare module 'flow-typed/dist/commands/create-stub' {
30
+ declare module.exports: any;
31
+ }
32
+
33
+ declare module 'flow-typed/dist/commands/install' {
34
+ declare module.exports: any;
35
+ }
36
+
37
+ declare module 'flow-typed/dist/commands/runTests' {
38
+ declare module.exports: any;
39
+ }
40
+
41
+ declare module 'flow-typed/dist/commands/search' {
42
+ declare module.exports: any;
43
+ }
44
+
45
+ declare module 'flow-typed/dist/commands/update-cache' {
46
+ declare module.exports: any;
47
+ }
48
+
49
+ declare module 'flow-typed/dist/commands/update' {
50
+ declare module.exports: any;
51
+ }
52
+
53
+ declare module 'flow-typed/dist/commands/validateDefs' {
54
+ declare module.exports: any;
55
+ }
56
+
57
+ declare module 'flow-typed/dist/commands/version' {
58
+ declare module.exports: any;
59
+ }
60
+
61
+ declare module 'flow-typed/dist/lib/cacheRepoUtils' {
62
+ declare module.exports: any;
63
+ }
64
+
65
+ declare module 'flow-typed/dist/lib/codeSign' {
66
+ declare module.exports: any;
67
+ }
68
+
69
+ declare module 'flow-typed/dist/lib/fileUtils' {
70
+ declare module.exports: any;
71
+ }
72
+
73
+ declare module 'flow-typed/dist/lib/flowProjectUtils' {
74
+ declare module.exports: any;
75
+ }
76
+
77
+ declare module 'flow-typed/dist/lib/flowVersion' {
78
+ declare module.exports: any;
79
+ }
80
+
81
+ declare module 'flow-typed/dist/lib/git' {
82
+ declare module.exports: any;
83
+ }
84
+
85
+ declare module 'flow-typed/dist/lib/github' {
86
+ declare module.exports: any;
87
+ }
88
+
89
+ declare module 'flow-typed/dist/lib/isInFlowTypedRepo' {
90
+ declare module.exports: any;
91
+ }
92
+
93
+ declare module 'flow-typed/dist/lib/libDefs' {
94
+ declare module.exports: any;
95
+ }
96
+
97
+ declare module 'flow-typed/dist/lib/node' {
98
+ declare module.exports: any;
99
+ }
100
+
101
+ declare module 'flow-typed/dist/lib/npm/npmLibDefs' {
102
+ declare module.exports: any;
103
+ }
104
+
105
+ declare module 'flow-typed/dist/lib/npm/npmProjectUtils' {
106
+ declare module.exports: any;
107
+ }
108
+
109
+ declare module 'flow-typed/dist/lib/semver' {
110
+ declare module.exports: any;
111
+ }
112
+
113
+ declare module 'flow-typed/dist/lib/stubUtils' {
114
+ declare module.exports: any;
115
+ }
116
+
117
+ declare module 'flow-typed/dist/lib/validationErrors' {
118
+ declare module.exports: any;
119
+ }
120
+
121
+ // Filename aliases
122
+ declare module 'flow-typed/dist/cli.js' {
123
+ declare module.exports: $Exports<'flow-typed/dist/cli'>;
124
+ }
125
+ declare module 'flow-typed/dist/commands/create-stub.js' {
126
+ declare module.exports: $Exports<'flow-typed/dist/commands/create-stub'>;
127
+ }
128
+ declare module 'flow-typed/dist/commands/install.js' {
129
+ declare module.exports: $Exports<'flow-typed/dist/commands/install'>;
130
+ }
131
+ declare module 'flow-typed/dist/commands/runTests.js' {
132
+ declare module.exports: $Exports<'flow-typed/dist/commands/runTests'>;
133
+ }
134
+ declare module 'flow-typed/dist/commands/search.js' {
135
+ declare module.exports: $Exports<'flow-typed/dist/commands/search'>;
136
+ }
137
+ declare module 'flow-typed/dist/commands/update-cache.js' {
138
+ declare module.exports: $Exports<'flow-typed/dist/commands/update-cache'>;
139
+ }
140
+ declare module 'flow-typed/dist/commands/update.js' {
141
+ declare module.exports: $Exports<'flow-typed/dist/commands/update'>;
142
+ }
143
+ declare module 'flow-typed/dist/commands/validateDefs.js' {
144
+ declare module.exports: $Exports<'flow-typed/dist/commands/validateDefs'>;
145
+ }
146
+ declare module 'flow-typed/dist/commands/version.js' {
147
+ declare module.exports: $Exports<'flow-typed/dist/commands/version'>;
148
+ }
149
+ declare module 'flow-typed/dist/lib/cacheRepoUtils.js' {
150
+ declare module.exports: $Exports<'flow-typed/dist/lib/cacheRepoUtils'>;
151
+ }
152
+ declare module 'flow-typed/dist/lib/codeSign.js' {
153
+ declare module.exports: $Exports<'flow-typed/dist/lib/codeSign'>;
154
+ }
155
+ declare module 'flow-typed/dist/lib/fileUtils.js' {
156
+ declare module.exports: $Exports<'flow-typed/dist/lib/fileUtils'>;
157
+ }
158
+ declare module 'flow-typed/dist/lib/flowProjectUtils.js' {
159
+ declare module.exports: $Exports<'flow-typed/dist/lib/flowProjectUtils'>;
160
+ }
161
+ declare module 'flow-typed/dist/lib/flowVersion.js' {
162
+ declare module.exports: $Exports<'flow-typed/dist/lib/flowVersion'>;
163
+ }
164
+ declare module 'flow-typed/dist/lib/git.js' {
165
+ declare module.exports: $Exports<'flow-typed/dist/lib/git'>;
166
+ }
167
+ declare module 'flow-typed/dist/lib/github.js' {
168
+ declare module.exports: $Exports<'flow-typed/dist/lib/github'>;
169
+ }
170
+ declare module 'flow-typed/dist/lib/isInFlowTypedRepo.js' {
171
+ declare module.exports: $Exports<'flow-typed/dist/lib/isInFlowTypedRepo'>;
172
+ }
173
+ declare module 'flow-typed/dist/lib/libDefs.js' {
174
+ declare module.exports: $Exports<'flow-typed/dist/lib/libDefs'>;
175
+ }
176
+ declare module 'flow-typed/dist/lib/node.js' {
177
+ declare module.exports: $Exports<'flow-typed/dist/lib/node'>;
178
+ }
179
+ declare module 'flow-typed/dist/lib/npm/npmLibDefs.js' {
180
+ declare module.exports: $Exports<'flow-typed/dist/lib/npm/npmLibDefs'>;
181
+ }
182
+ declare module 'flow-typed/dist/lib/npm/npmProjectUtils.js' {
183
+ declare module.exports: $Exports<'flow-typed/dist/lib/npm/npmProjectUtils'>;
184
+ }
185
+ declare module 'flow-typed/dist/lib/semver.js' {
186
+ declare module.exports: $Exports<'flow-typed/dist/lib/semver'>;
187
+ }
188
+ declare module 'flow-typed/dist/lib/stubUtils.js' {
189
+ declare module.exports: $Exports<'flow-typed/dist/lib/stubUtils'>;
190
+ }
191
+ declare module 'flow-typed/dist/lib/validationErrors.js' {
192
+ declare module.exports: $Exports<'flow-typed/dist/lib/validationErrors'>;
193
+ }
@@ -0,0 +1,483 @@
1
+ // @flow
2
+ /* eslint-disable prefer-template */
3
+
4
+ import Transport from "@ledgerhq/hw-transport";
5
+ import {
6
+ BleManager,
7
+ ConnectionPriority,
8
+ BleErrorCode,
9
+ } from "react-native-ble-plx";
10
+ import {
11
+ getBluetoothServiceUuids,
12
+ getInfosForServiceUuid,
13
+ } from "@ledgerhq/devices";
14
+ import type { DeviceModel } from "@ledgerhq/devices";
15
+
16
+ import { sendAPDU } from "@ledgerhq/devices/lib/ble/sendAPDU";
17
+ import { receiveAPDU } from "@ledgerhq/devices/lib/ble/receiveAPDU";
18
+ import { log } from "@ledgerhq/logs";
19
+ import { Observable, defer, merge, from } from "rxjs";
20
+ import { share, ignoreElements, first, map, tap } from "rxjs/operators";
21
+ import {
22
+ CantOpenDevice,
23
+ TransportError,
24
+ DisconnectedDeviceDuringOperation,
25
+ } from "@ledgerhq/errors";
26
+ import type { Device, Characteristic } from "./types";
27
+ import { monitorCharacteristic } from "./monitorCharacteristic";
28
+ import { awaitsBleOn } from "./awaitsBleOn";
29
+ import { decoratePromiseErrors, remapError } from "./remapErrors";
30
+
31
+ let connectOptions = {
32
+ requestMTU: 156,
33
+ };
34
+
35
+ const transportsCache = {};
36
+ const bleManager = new BleManager();
37
+
38
+ const retrieveInfos = (device) => {
39
+ if (!device || !device.serviceUUIDs) return;
40
+ const [serviceUUID] = device.serviceUUIDs;
41
+ if (!serviceUUID) return;
42
+ const infos = getInfosForServiceUuid(serviceUUID);
43
+ if (!infos) return;
44
+ return infos;
45
+ };
46
+
47
+ type ReconnectionConfig = {
48
+ pairingThreshold: number,
49
+ delayAfterFirstPairing: number,
50
+ };
51
+ let reconnectionConfig: ?ReconnectionConfig = {
52
+ pairingThreshold: 1000,
53
+ delayAfterFirstPairing: 4000,
54
+ };
55
+ export function setReconnectionConfig(config: ?ReconnectionConfig) {
56
+ reconnectionConfig = config;
57
+ }
58
+
59
+ const delay = (ms) => new Promise((success) => setTimeout(success, ms));
60
+
61
+ async function open(deviceOrId: Device | string, needsReconnect: boolean) {
62
+ let device;
63
+ if (typeof deviceOrId === "string") {
64
+ if (transportsCache[deviceOrId]) {
65
+ log("ble-verbose", "Transport in cache, using that.");
66
+ return transportsCache[deviceOrId];
67
+ }
68
+
69
+ log("ble-verbose", `open(${deviceOrId})`);
70
+
71
+ await awaitsBleOn(bleManager);
72
+
73
+ if (!device) {
74
+ // works for iOS but not Android
75
+ const devices = await bleManager.devices([deviceOrId]);
76
+ log("ble-verbose", `found ${devices.length} devices`);
77
+ [device] = devices;
78
+ }
79
+
80
+ if (!device) {
81
+ const connectedDevices = await bleManager.connectedDevices(
82
+ getBluetoothServiceUuids()
83
+ );
84
+ const connectedDevicesFiltered = connectedDevices.filter(
85
+ (d) => d.id === deviceOrId
86
+ );
87
+ log(
88
+ "ble-verbose",
89
+ `found ${connectedDevicesFiltered.length} connected devices`
90
+ );
91
+ [device] = connectedDevicesFiltered;
92
+ }
93
+
94
+ if (!device) {
95
+ log("ble-verbose", `connectToDevice(${deviceOrId})`);
96
+ try {
97
+ device = await bleManager.connectToDevice(deviceOrId, connectOptions);
98
+ } catch (e) {
99
+ if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
100
+ // eslint-disable-next-line require-atomic-updates
101
+ connectOptions = {};
102
+ device = await bleManager.connectToDevice(deviceOrId);
103
+ } else {
104
+ throw e;
105
+ }
106
+ }
107
+ }
108
+
109
+ if (!device) {
110
+ throw new CantOpenDevice();
111
+ }
112
+ } else {
113
+ device = deviceOrId;
114
+ }
115
+
116
+ if (!(await device.isConnected())) {
117
+ log("ble-verbose", "not connected. connecting...");
118
+ try {
119
+ await device.connect(connectOptions);
120
+ } catch (e) {
121
+ if (e.errorCode === BleErrorCode.DeviceMTUChangeFailed) {
122
+ // eslint-disable-next-line require-atomic-updates
123
+ connectOptions = {};
124
+ await device.connect();
125
+ } else {
126
+ throw e;
127
+ }
128
+ }
129
+ }
130
+
131
+ await device.discoverAllServicesAndCharacteristics();
132
+
133
+ let res = retrieveInfos(device);
134
+ let characteristics;
135
+ if (!res) {
136
+ for (const uuid of getBluetoothServiceUuids()) {
137
+ try {
138
+ characteristics = await device.characteristicsForService(uuid);
139
+ res = getInfosForServiceUuid(uuid);
140
+ break;
141
+ } catch (e) {
142
+ // we attempt to connect to service
143
+ }
144
+ }
145
+ }
146
+ if (!res) {
147
+ throw new TransportError("service not found", "BLEServiceNotFound");
148
+ }
149
+
150
+ const { deviceModel, serviceUuid, writeUuid, notifyUuid } = res;
151
+
152
+ if (!characteristics) {
153
+ characteristics = await device.characteristicsForService(serviceUuid);
154
+ }
155
+
156
+ if (!characteristics) {
157
+ throw new TransportError("service not found", "BLEServiceNotFound");
158
+ }
159
+ let writeC;
160
+ let notifyC;
161
+ for (const c of characteristics) {
162
+ if (c.uuid === writeUuid) {
163
+ writeC = c;
164
+ } else if (c.uuid === notifyUuid) {
165
+ notifyC = c;
166
+ }
167
+ }
168
+ if (!writeC) {
169
+ throw new TransportError(
170
+ "write characteristic not found",
171
+ "BLEChracteristicNotFound"
172
+ );
173
+ }
174
+ if (!notifyC) {
175
+ throw new TransportError(
176
+ "notify characteristic not found",
177
+ "BLEChracteristicNotFound"
178
+ );
179
+ }
180
+ if (!writeC.isWritableWithResponse) {
181
+ throw new TransportError(
182
+ "write characteristic not writableWithResponse",
183
+ "BLEChracteristicInvalid"
184
+ );
185
+ }
186
+ if (!notifyC.isNotifiable) {
187
+ throw new TransportError(
188
+ "notify characteristic not notifiable",
189
+ "BLEChracteristicInvalid"
190
+ );
191
+ }
192
+
193
+ log("ble-verbose", `device.mtu=${device.mtu}`);
194
+
195
+ const notifyObservable = monitorCharacteristic(notifyC).pipe(
196
+ tap((value) => {
197
+ log("ble-frame", "<= " + value.toString("hex"));
198
+ }),
199
+ share()
200
+ );
201
+
202
+ const notif = notifyObservable.subscribe();
203
+
204
+ const transport = new BluetoothTransport(
205
+ device,
206
+ writeC,
207
+ notifyObservable,
208
+ deviceModel
209
+ );
210
+
211
+ const onDisconnect = (e) => {
212
+ transport.notYetDisconnected = false;
213
+ notif.unsubscribe();
214
+ disconnectedSub.remove();
215
+ delete transportsCache[transport.id];
216
+ log("ble-verbose", `BleTransport(${transport.id}) disconnected`);
217
+ transport.emit("disconnect", e);
218
+ };
219
+
220
+ // eslint-disable-next-line require-atomic-updates
221
+ transportsCache[transport.id] = transport;
222
+ const disconnectedSub = device.onDisconnected((e) => {
223
+ if (!transport.notYetDisconnected) return;
224
+ onDisconnect(e);
225
+ });
226
+
227
+ let beforeMTUTime = Date.now();
228
+ try {
229
+ await transport.inferMTU();
230
+ } finally {
231
+ let afterMTUTime = Date.now();
232
+
233
+ if (reconnectionConfig) {
234
+ // workaround for #279: we need to open() again if we come the first time here,
235
+ // to make sure we do a disconnect() after the first pairing time
236
+ // because of a firmware bug
237
+
238
+ if (afterMTUTime - beforeMTUTime < reconnectionConfig.pairingThreshold) {
239
+ needsReconnect = false; // (optim) there is likely no new pairing done because mtu answer was fast.
240
+ }
241
+
242
+ if (needsReconnect) {
243
+ // necessary time for the bonding workaround
244
+ await BluetoothTransport.disconnect(transport.id).catch(() => {});
245
+ await delay(reconnectionConfig.delayAfterFirstPairing);
246
+ }
247
+ } else {
248
+ needsReconnect = false;
249
+ }
250
+ }
251
+
252
+ if (needsReconnect) {
253
+ return open(device, false);
254
+ }
255
+
256
+ return transport;
257
+ }
258
+
259
+ /**
260
+ * react-native bluetooth BLE implementation
261
+ * @example
262
+ * import BluetoothTransport from "@ledgerhq/react-native-hw-transport-ble";
263
+ */
264
+ export default class BluetoothTransport extends Transport<Device | string> {
265
+ /**
266
+ *
267
+ */
268
+ static isSupported = (): Promise<boolean> =>
269
+ Promise.resolve(typeof BleManager === "function");
270
+
271
+ /**
272
+ *
273
+ */
274
+ static setLogLevel = (level: *) => {
275
+ bleManager.setLogLevel(level);
276
+ };
277
+
278
+ /**
279
+ * TODO could add this concept in all transports
280
+ * observe event with { available: bool, string } // available is generic, type is specific
281
+ * an event is emit once and then listened
282
+ */
283
+ static observeState(observer: *) {
284
+ const emitFromState = (type) => {
285
+ observer.next({ type, available: type === "PoweredOn" });
286
+ };
287
+ bleManager.onStateChange(emitFromState, true);
288
+ return {
289
+ unsubscribe: () => {},
290
+ };
291
+ }
292
+
293
+ static list = (): * => {
294
+ throw new Error("not implemented");
295
+ };
296
+
297
+ /**
298
+ * Scan for bluetooth Ledger devices
299
+ */
300
+ static listen(observer: *) {
301
+ log("ble-verbose", "listen...");
302
+ let unsubscribed;
303
+
304
+ // $FlowFixMe
305
+ const stateSub = bleManager.onStateChange(async (state) => {
306
+ if (state === "PoweredOn") {
307
+ stateSub.remove();
308
+
309
+ const devices = await bleManager.connectedDevices(
310
+ getBluetoothServiceUuids()
311
+ );
312
+ if (unsubscribed) return;
313
+
314
+ await Promise.all(
315
+ devices.map((d) =>
316
+ BluetoothTransport.disconnect(d.id).catch(() => {})
317
+ )
318
+ );
319
+ if (unsubscribed) return;
320
+
321
+ bleManager.startDeviceScan(
322
+ getBluetoothServiceUuids(),
323
+ null,
324
+ (bleError, device) => {
325
+ if (bleError) {
326
+ observer.error(bleError);
327
+ unsubscribe();
328
+ return;
329
+ }
330
+ const res = retrieveInfos(device);
331
+ const deviceModel = res && res.deviceModel;
332
+ observer.next({ type: "add", descriptor: device, deviceModel });
333
+ }
334
+ );
335
+ }
336
+ }, true);
337
+ const unsubscribe = () => {
338
+ unsubscribed = true;
339
+ bleManager.stopDeviceScan();
340
+ stateSub.remove();
341
+ log("ble-verbose", "done listening.");
342
+ };
343
+ return { unsubscribe };
344
+ }
345
+
346
+ /**
347
+ * Open a BLE transport
348
+ * @param {*} deviceOrId
349
+ */
350
+ static async open(deviceOrId: Device | string) {
351
+ return open(deviceOrId, true);
352
+ }
353
+
354
+ /**
355
+ * Globally disconnect a BLE device by its ID
356
+ */
357
+ static disconnect = async (id: *) => {
358
+ log("ble-verbose", `user disconnect(${id})`);
359
+ await bleManager.cancelDeviceConnection(id);
360
+ };
361
+
362
+ id: string;
363
+
364
+ device: Device;
365
+
366
+ mtuSize: number = 20;
367
+
368
+ writeCharacteristic: Characteristic;
369
+
370
+ notifyObservable: Observable<Buffer>;
371
+
372
+ deviceModel: DeviceModel;
373
+
374
+ notYetDisconnected = true;
375
+
376
+ constructor(
377
+ device: Device,
378
+ writeCharacteristic: Characteristic,
379
+ notifyObservable: Observable<Buffer>,
380
+ deviceModel: DeviceModel
381
+ ) {
382
+ super();
383
+ this.id = device.id;
384
+ this.device = device;
385
+ this.writeCharacteristic = writeCharacteristic;
386
+ this.notifyObservable = notifyObservable;
387
+ this.deviceModel = deviceModel;
388
+ log("ble-verbose", `BleTransport(${String(this.id)}) new instance`);
389
+ }
390
+
391
+ /**
392
+ * communicate with a BLE transport
393
+ */
394
+ exchange = (apdu: Buffer): Promise<Buffer> =>
395
+ this.exchangeAtomicImpl(async () => {
396
+ try {
397
+ const msgIn = apdu.toString("hex");
398
+ log("apdu", `=> ${msgIn}`);
399
+
400
+ const data = await merge(
401
+ // $FlowFixMe
402
+ this.notifyObservable.pipe(receiveAPDU),
403
+ sendAPDU(this.write, apdu, this.mtuSize)
404
+ ).toPromise();
405
+
406
+ const msgOut = data.toString("hex");
407
+ log("apdu", `<= ${msgOut}`);
408
+
409
+ return data;
410
+ } catch (e) {
411
+ log("ble-error", "exchange got " + String(e));
412
+ if (this.notYetDisconnected) {
413
+ // in such case we will always disconnect because something is bad.
414
+ await bleManager.cancelDeviceConnection(this.id).catch(() => {}); // but we ignore if disconnect worked.
415
+ }
416
+ throw remapError(e);
417
+ }
418
+ });
419
+
420
+ // TODO we probably will do this at end of open
421
+ async inferMTU() {
422
+ let { mtu } = this.device;
423
+ await this.exchangeAtomicImpl(async () => {
424
+ try {
425
+ mtu =
426
+ (await merge(
427
+ this.notifyObservable.pipe(
428
+ first((buffer) => buffer.readUInt8(0) === 0x08),
429
+ map((buffer) => buffer.readUInt8(5))
430
+ ),
431
+ defer(() => from(this.write(Buffer.from([0x08, 0, 0, 0, 0])))).pipe(
432
+ ignoreElements()
433
+ )
434
+ ).toPromise()) + 3;
435
+ } catch (e) {
436
+ log("ble-error", "inferMTU got " + String(e));
437
+ await bleManager.cancelDeviceConnection(this.id).catch(() => {}); // but we ignore if disconnect worked.
438
+ throw remapError(e);
439
+ }
440
+ });
441
+
442
+ if (mtu > 23) {
443
+ const mtuSize = mtu - 3;
444
+ log(
445
+ "ble-verbose",
446
+ `BleTransport(${String(this.id)}) mtu set to ${String(mtuSize)}`
447
+ );
448
+ this.mtuSize = mtuSize;
449
+ }
450
+
451
+ return this.mtuSize;
452
+ }
453
+
454
+ async requestConnectionPriority(
455
+ connectionPriority: "Balanced" | "High" | "LowPower"
456
+ ) {
457
+ await decoratePromiseErrors(
458
+ this.device.requestConnectionPriority(
459
+ ConnectionPriority[connectionPriority]
460
+ )
461
+ );
462
+ }
463
+
464
+ setScrambleKey() {}
465
+
466
+ write = async (buffer: Buffer, txid?: ?string) => {
467
+ log("ble-frame", "=> " + buffer.toString("hex"));
468
+ try {
469
+ await this.writeCharacteristic.writeWithResponse(
470
+ buffer.toString("base64"),
471
+ txid
472
+ );
473
+ } catch (e) {
474
+ throw new DisconnectedDeviceDuringOperation(e.message);
475
+ }
476
+ };
477
+
478
+ async close() {
479
+ if (this.exchangeBusyPromise) {
480
+ await this.exchangeBusyPromise;
481
+ }
482
+ }
483
+ }
@@ -0,0 +1,33 @@
1
+ // @flow
2
+
3
+ import { BluetoothRequired } from "@ledgerhq/errors";
4
+ import { log } from "@ledgerhq/logs";
5
+ import timer from "./timer";
6
+ import type { BleManager } from "./types";
7
+
8
+ export const awaitsBleOn = (
9
+ bleManager: BleManager,
10
+ ms: number = 3000
11
+ ): Promise<void> =>
12
+ new Promise((resolve, reject) => {
13
+ let done = false;
14
+ let lastState = "Unknown";
15
+
16
+ const stateSub = bleManager.onStateChange((state) => {
17
+ lastState = state;
18
+ log("ble-verbose", `ble state -> ${state}`);
19
+ if (state === "PoweredOn") {
20
+ if (done) return;
21
+ removeTimeout();
22
+ done = true;
23
+ stateSub.remove();
24
+ resolve();
25
+ }
26
+ }, true);
27
+
28
+ const removeTimeout = timer.timeout(() => {
29
+ if (done) return;
30
+ stateSub.remove();
31
+ reject(new BluetoothRequired("", { state: lastState }));
32
+ }, ms);
33
+ });
@@ -0,0 +1,41 @@
1
+ // @flow
2
+
3
+ import { Observable } from "rxjs";
4
+ import { TransportError } from "@ledgerhq/errors";
5
+ import type { Characteristic } from "./types";
6
+ import { log } from "@ledgerhq/logs";
7
+
8
+ export const monitorCharacteristic = (
9
+ characteristic: Characteristic
10
+ ): Observable<Buffer> =>
11
+ Observable.create((o) => {
12
+ log("ble-verbose", "start monitor " + characteristic.uuid);
13
+ const subscription = characteristic.monitor((error, c) => {
14
+ if (error) {
15
+ log(
16
+ "ble-verbose",
17
+ "error monitor " + characteristic.uuid + ": " + error
18
+ );
19
+ o.error(error);
20
+ } else if (!c) {
21
+ o.error(
22
+ new TransportError(
23
+ "characteristic monitor null value",
24
+ "CharacteristicMonitorNull"
25
+ )
26
+ );
27
+ } else {
28
+ try {
29
+ const value = Buffer.from(c.value, "base64");
30
+ o.next(value);
31
+ } catch (error) {
32
+ o.error(error);
33
+ }
34
+ }
35
+ });
36
+
37
+ return () => {
38
+ log("ble-verbose", "end monitor " + characteristic.uuid);
39
+ subscription.remove();
40
+ };
41
+ });
@@ -0,0 +1,20 @@
1
+ // @flow
2
+ import { DisconnectedDevice } from "@ledgerhq/errors";
3
+
4
+ export const remapError = (error: ?Error) => {
5
+ if (!error || !error.message) return error;
6
+ if (
7
+ error.message.includes("was disconnected") ||
8
+ error.message.includes("not found")
9
+ ) {
10
+ return new DisconnectedDevice();
11
+ }
12
+ return error;
13
+ };
14
+
15
+ export const rethrowError = (e: ?Error) => {
16
+ throw remapError(e);
17
+ };
18
+
19
+ export const decoratePromiseErrors = <A>(promise: Promise<A>): Promise<A> =>
20
+ promise.catch(rethrowError);
@@ -0,0 +1,27 @@
1
+ // @flow
2
+
3
+ const timer =
4
+ process.env.NODE_ENV === "development"
5
+ ? {
6
+ timeout: (fn: Function, ms: number) => {
7
+ // hack for a bug in RN https://github.com/facebook/react-native/issues/9030
8
+ const startTime = Date.now();
9
+ const interval = setInterval(() => {
10
+ if (Date.now() - startTime >= ms) {
11
+ clearInterval(interval);
12
+ fn();
13
+ }
14
+ }, 100);
15
+ return () => {
16
+ clearInterval(interval);
17
+ };
18
+ },
19
+ }
20
+ : {
21
+ timeout: (fn: Function, ms: number) => {
22
+ const timeout = setTimeout(fn, ms);
23
+ return () => clearTimeout(timeout);
24
+ },
25
+ };
26
+
27
+ export default timer;
@@ -0,0 +1,4 @@
1
+ // @flow
2
+ export type { BleManager } from "react-native-ble-plx";
3
+ export type Device = *;
4
+ export type Characteristic = *;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/react-native-hw-transport-ble",
3
- "version": "6.24.1",
3
+ "version": "6.25.1-alpha.3+eb669e17",
4
4
  "description": "Ledger Hardware Wallet Bluetooth BLE transport for React Native",
5
5
  "keywords": [
6
6
  "Ledger",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@ledgerhq/devices": "^6.24.1",
29
29
  "@ledgerhq/errors": "^6.10.0",
30
- "@ledgerhq/hw-transport": "^6.24.1",
30
+ "@ledgerhq/hw-transport": "^6.25.1-alpha.3+eb669e17",
31
31
  "@ledgerhq/logs": "^6.10.0",
32
32
  "invariant": "^2.2.4",
33
33
  "react-native-ble-plx": "2.0.3",
@@ -39,5 +39,5 @@
39
39
  "build": "bash ../../script/build.sh",
40
40
  "watch": "bash ../../script/watch.sh"
41
41
  },
42
- "gitHead": "159269dafc5f177c7af5b20761ab0ef3550e3faf"
42
+ "gitHead": "eb669e17dd87d3ab568beab1f9a5ddb1a2536e83"
43
43
  }