@ledgerhq/hw-transport-webhid 6.11.2 → 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,223 @@
1
+ //@flow
2
+ import Transport from "@ledgerhq/hw-transport";
3
+ import type {
4
+ Observer,
5
+ DescriptorEvent,
6
+ Subscription,
7
+ } from "@ledgerhq/hw-transport";
8
+ import hidFraming from "@ledgerhq/devices/lib/hid-framing";
9
+ import { identifyUSBProductId, ledgerUSBVendorId } from "@ledgerhq/devices";
10
+ import type { DeviceModel } from "@ledgerhq/devices";
11
+ import { log } from "@ledgerhq/logs";
12
+ import {
13
+ TransportOpenUserCancelled,
14
+ DisconnectedDeviceDuringOperation,
15
+ DisconnectedDevice,
16
+ TransportError,
17
+ } from "@ledgerhq/errors";
18
+
19
+ const ledgerDevices = [{ vendorId: ledgerUSBVendorId }];
20
+
21
+ const isSupported = () =>
22
+ Promise.resolve(!!(global.navigator && global.navigator.hid));
23
+
24
+ const getHID = (): HID => {
25
+ // $FlowFixMe
26
+ const { hid } = navigator;
27
+ if (!hid)
28
+ throw new TransportError(
29
+ "navigator.hid is not supported",
30
+ "HIDNotSupported"
31
+ );
32
+ return hid;
33
+ };
34
+
35
+ async function requestLedgerDevices(): Promise<HIDDevice[]> {
36
+ const device = await getHID().requestDevice({ filters: ledgerDevices });
37
+ if (Array.isArray(device)) return device;
38
+ return [device];
39
+ }
40
+
41
+ async function getLedgerDevices(): Promise<HIDDevice[]> {
42
+ const devices = await getHID().getDevices();
43
+ return devices.filter((d) => d.vendorId === ledgerUSBVendorId);
44
+ }
45
+
46
+ async function getFirstLedgerDevice(): Promise<HIDDevice> {
47
+ const existingDevices = await getLedgerDevices();
48
+ if (existingDevices.length > 0) return existingDevices[0];
49
+ const devices = await requestLedgerDevices();
50
+ return devices[0];
51
+ }
52
+
53
+ /**
54
+ * WebHID Transport implementation
55
+ * @example
56
+ * import TransportWebHID from "@ledgerhq/hw-transport-webhid";
57
+ * ...
58
+ * TransportWebHID.create().then(transport => ...)
59
+ */
60
+ export default class TransportWebHID extends Transport<HIDDevice> {
61
+ device: HIDDevice;
62
+ deviceModel: ?DeviceModel;
63
+ channel = Math.floor(Math.random() * 0xffff);
64
+ packetSize = 64;
65
+
66
+ constructor(device: HIDDevice) {
67
+ super();
68
+ this.device = device;
69
+ this.deviceModel = identifyUSBProductId(device.productId);
70
+ device.addEventListener("inputreport", this.onInputReport);
71
+ }
72
+
73
+ inputs = [];
74
+ inputCallback: ?(Buffer) => void;
75
+
76
+ read = (): Promise<Buffer> => {
77
+ if (this.inputs.length) {
78
+ return Promise.resolve(this.inputs.shift());
79
+ }
80
+ return new Promise((success) => {
81
+ this.inputCallback = success;
82
+ });
83
+ };
84
+
85
+ onInputReport = (e: InputReportEvent) => {
86
+ const buffer = Buffer.from(e.data.buffer);
87
+ if (this.inputCallback) {
88
+ this.inputCallback(buffer);
89
+ this.inputCallback = null;
90
+ } else {
91
+ this.inputs.push(buffer);
92
+ }
93
+ };
94
+
95
+ /**
96
+ * Check if WebUSB transport is supported.
97
+ */
98
+ static isSupported = isSupported;
99
+
100
+ /**
101
+ * List the WebUSB devices that was previously authorized by the user.
102
+ */
103
+ static list = getLedgerDevices;
104
+
105
+ /**
106
+ * Actively listen to WebUSB devices and emit ONE device
107
+ * that was either accepted before, if not it will trigger the native permission UI.
108
+ *
109
+ * Important: it must be called in the context of a UI click!
110
+ */
111
+ static listen = (
112
+ observer: Observer<DescriptorEvent<HIDDevice>>
113
+ ): Subscription => {
114
+ let unsubscribed = false;
115
+ getFirstLedgerDevice().then(
116
+ (device) => {
117
+ if (!device) {
118
+ observer.error(
119
+ new TransportOpenUserCancelled("Access denied to use Ledger device")
120
+ );
121
+ } else if (!unsubscribed) {
122
+ const deviceModel = identifyUSBProductId(device.productId);
123
+ observer.next({ type: "add", descriptor: device, deviceModel });
124
+ observer.complete();
125
+ }
126
+ },
127
+ (error) => {
128
+ observer.error(new TransportOpenUserCancelled(error.message));
129
+ }
130
+ );
131
+ function unsubscribe() {
132
+ unsubscribed = true;
133
+ }
134
+ return { unsubscribe };
135
+ };
136
+
137
+ /**
138
+ * Similar to create() except it will always display the device permission (even if some devices are already accepted).
139
+ */
140
+ static async request() {
141
+ const [device] = await requestLedgerDevices();
142
+ return TransportWebHID.open(device);
143
+ }
144
+
145
+ /**
146
+ * Similar to create() except it will never display the device permission (it returns a Promise<?Transport>, null if it fails to find a device).
147
+ */
148
+ static async openConnected() {
149
+ const devices = await getLedgerDevices();
150
+ if (devices.length === 0) return null;
151
+ return TransportWebHID.open(devices[0]);
152
+ }
153
+
154
+ /**
155
+ * Create a Ledger transport with a HIDDevice
156
+ */
157
+ static async open(device: HIDDevice) {
158
+ await device.open();
159
+ const transport = new TransportWebHID(device);
160
+ const onDisconnect = (e) => {
161
+ if (device === e.device) {
162
+ getHID().removeEventListener("disconnect", onDisconnect);
163
+ transport._emitDisconnect(new DisconnectedDevice());
164
+ }
165
+ };
166
+ getHID().addEventListener("disconnect", onDisconnect);
167
+ return transport;
168
+ }
169
+
170
+ _disconnectEmitted = false;
171
+ _emitDisconnect = (e: Error) => {
172
+ if (this._disconnectEmitted) return;
173
+ this._disconnectEmitted = true;
174
+ this.emit("disconnect", e);
175
+ };
176
+
177
+ /**
178
+ * Release the transport device
179
+ */
180
+ async close(): Promise<void> {
181
+ await this.exchangeBusyPromise;
182
+ this.device.removeEventListener("inputreport", this.onInputReport);
183
+ await this.device.close();
184
+ }
185
+
186
+ /**
187
+ * Exchange with the device using APDU protocol.
188
+ * @param apdu
189
+ * @returns a promise of apdu response
190
+ */
191
+ exchange = (apdu: Buffer): Promise<Buffer> =>
192
+ this.exchangeAtomicImpl(async () => {
193
+ const { channel, packetSize } = this;
194
+ log("apdu", "=> " + apdu.toString("hex"));
195
+
196
+ const framing = hidFraming(channel, packetSize);
197
+
198
+ // Write...
199
+ const blocks = framing.makeBlocks(apdu);
200
+ for (let i = 0; i < blocks.length; i++) {
201
+ await this.device.sendReport(0, blocks[i]);
202
+ }
203
+
204
+ // Read...
205
+ let result;
206
+ let acc;
207
+ while (!(result = framing.getReducedResult(acc))) {
208
+ const buffer = await this.read();
209
+ acc = framing.reduceResponse(acc, buffer);
210
+ }
211
+
212
+ log("apdu", "<= " + result.toString("hex"));
213
+ return result;
214
+ }).catch((e) => {
215
+ if (e && e.message && e.message.includes("write")) {
216
+ this._emitDisconnect(e);
217
+ throw new DisconnectedDeviceDuringOperation(e.message);
218
+ }
219
+ throw e;
220
+ });
221
+
222
+ setScrambleKey() {}
223
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/hw-transport-webhid",
3
- "version": "6.11.2",
3
+ "version": "6.25.1-alpha.3+eb669e17",
4
4
  "description": "Ledger Hardware Wallet WebHID implementation of the communication layer",
5
5
  "keywords": [
6
6
  "Ledger",
@@ -26,9 +26,9 @@
26
26
  "types": "lib/TransportWebHID.d.ts",
27
27
  "license": "Apache-2.0",
28
28
  "dependencies": {
29
- "@ledgerhq/devices": "^6.11.2",
29
+ "@ledgerhq/devices": "^6.24.1",
30
30
  "@ledgerhq/errors": "^6.10.0",
31
- "@ledgerhq/hw-transport": "^6.11.2",
31
+ "@ledgerhq/hw-transport": "^6.25.1-alpha.3+eb669e17",
32
32
  "@ledgerhq/logs": "^6.10.0"
33
33
  },
34
34
  "devDependencies": {
@@ -41,5 +41,5 @@
41
41
  "watch": "bash ../../script/watch.sh",
42
42
  "doc": "bash ../../script/doc.sh"
43
43
  },
44
- "gitHead": "d243550a31bd781924fc301492320b16212c3dbe"
44
+ "gitHead": "eb669e17dd87d3ab568beab1f9a5ddb1a2536e83"
45
45
  }