@ledgerhq/hw-transport-node-hid 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.
- package/lib-es/TransportNodeHid.js.flow +125 -0
- package/lib-es/listenDevices.js.flow +107 -0
- package/package.json +5 -5
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//@flow
|
|
2
|
+
|
|
3
|
+
import HID from "node-hid";
|
|
4
|
+
import TransportNodeHidNoEvents, {
|
|
5
|
+
getDevices,
|
|
6
|
+
} from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
7
|
+
import type {
|
|
8
|
+
Observer,
|
|
9
|
+
DescriptorEvent,
|
|
10
|
+
Subscription,
|
|
11
|
+
} from "@ledgerhq/hw-transport";
|
|
12
|
+
import { identifyUSBProductId } from "@ledgerhq/devices";
|
|
13
|
+
import { TransportError } from "@ledgerhq/errors";
|
|
14
|
+
import listenDevices from "./listenDevices";
|
|
15
|
+
|
|
16
|
+
let listenDevicesDebounce = 500;
|
|
17
|
+
let listenDevicesPollingSkip = () => false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* node-hid Transport implementation
|
|
21
|
+
* @example
|
|
22
|
+
* import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
|
|
23
|
+
* ...
|
|
24
|
+
* TransportNodeHid.create().then(transport => ...)
|
|
25
|
+
*/
|
|
26
|
+
export default class TransportNodeHid extends TransportNodeHidNoEvents {
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
*/
|
|
30
|
+
static isSupported = TransportNodeHidNoEvents.isSupported;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
static list = TransportNodeHidNoEvents.list;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
40
|
+
static setListenDevicesDebounce = (delay: number) => {
|
|
41
|
+
listenDevicesDebounce = delay;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
static setListenDevicesPollingSkip = (conditionToSkip: () => boolean) => {
|
|
48
|
+
listenDevicesPollingSkip = conditionToSkip;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
static setListenDevicesDebug = () => {
|
|
55
|
+
console.warn(
|
|
56
|
+
"setListenDevicesDebug is deprecated. Use @ledgerhq/logs instead. No logs will get emitted there anymore."
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
*/
|
|
62
|
+
static listen = (
|
|
63
|
+
observer: Observer<DescriptorEvent<?string>>
|
|
64
|
+
): Subscription => {
|
|
65
|
+
let unsubscribed = false;
|
|
66
|
+
Promise.resolve(getDevices()).then((devices) => {
|
|
67
|
+
// this needs to run asynchronously so the subscription is defined during this phase
|
|
68
|
+
for (const device of devices) {
|
|
69
|
+
if (!unsubscribed) {
|
|
70
|
+
const descriptor: string = device.path;
|
|
71
|
+
const deviceModel = identifyUSBProductId(device.productId);
|
|
72
|
+
observer.next({ type: "add", descriptor, device, deviceModel });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const { events, stop } = listenDevices(
|
|
77
|
+
listenDevicesDebounce,
|
|
78
|
+
listenDevicesPollingSkip
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const onAdd = (device) => {
|
|
82
|
+
if (unsubscribed || !device) return;
|
|
83
|
+
const deviceModel = identifyUSBProductId(device.productId);
|
|
84
|
+
observer.next({
|
|
85
|
+
type: "add",
|
|
86
|
+
descriptor: device.path,
|
|
87
|
+
deviceModel,
|
|
88
|
+
device,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const onRemove = (device) => {
|
|
92
|
+
if (unsubscribed || !device) return;
|
|
93
|
+
const deviceModel = identifyUSBProductId(device.productId);
|
|
94
|
+
observer.next({
|
|
95
|
+
type: "remove",
|
|
96
|
+
descriptor: device.path,
|
|
97
|
+
deviceModel,
|
|
98
|
+
device,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
events.on("add", onAdd);
|
|
102
|
+
events.on("remove", onRemove);
|
|
103
|
+
function unsubscribe() {
|
|
104
|
+
unsubscribed = true;
|
|
105
|
+
events.removeListener("add", onAdd);
|
|
106
|
+
events.removeListener("remove", onRemove);
|
|
107
|
+
stop();
|
|
108
|
+
}
|
|
109
|
+
return { unsubscribe };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* if path="" is not provided, the library will take the first device
|
|
114
|
+
*/
|
|
115
|
+
static open(path: ?string) {
|
|
116
|
+
return Promise.resolve().then(() => {
|
|
117
|
+
if (path) {
|
|
118
|
+
return new TransportNodeHid(new HID.HID(path));
|
|
119
|
+
}
|
|
120
|
+
const device = getDevices()[0];
|
|
121
|
+
if (!device) throw new TransportError("NoDevice", "NoDevice");
|
|
122
|
+
return new TransportNodeHid(new HID.HID(device.path));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import EventEmitter from "events";
|
|
4
|
+
import { getDevices } from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
5
|
+
import { log } from "@ledgerhq/logs";
|
|
6
|
+
import usb from "usb";
|
|
7
|
+
import debounce from "lodash/debounce";
|
|
8
|
+
|
|
9
|
+
export default (
|
|
10
|
+
delay: number,
|
|
11
|
+
listenDevicesPollingSkip: () => boolean
|
|
12
|
+
): ({
|
|
13
|
+
events: EventEmitter,
|
|
14
|
+
stop: () => void,
|
|
15
|
+
}) => {
|
|
16
|
+
const events = new EventEmitter();
|
|
17
|
+
events.setMaxListeners(0);
|
|
18
|
+
|
|
19
|
+
let listDevices = getDevices();
|
|
20
|
+
|
|
21
|
+
const flatDevice = (d) => d.path;
|
|
22
|
+
|
|
23
|
+
const getFlatDevices = () => [
|
|
24
|
+
...new Set(getDevices().map((d) => flatDevice(d))),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const getDeviceByPaths = (paths) =>
|
|
28
|
+
listDevices.find((d) => paths.includes(flatDevice(d)));
|
|
29
|
+
|
|
30
|
+
let lastDevices = getFlatDevices();
|
|
31
|
+
|
|
32
|
+
const poll = () => {
|
|
33
|
+
if (!listenDevicesPollingSkip()) {
|
|
34
|
+
log("hid-listen", "Polling for added or removed devices");
|
|
35
|
+
|
|
36
|
+
let changeFound = false;
|
|
37
|
+
const currentDevices = getFlatDevices();
|
|
38
|
+
const newDevices = currentDevices.filter((d) => !lastDevices.includes(d));
|
|
39
|
+
|
|
40
|
+
if (newDevices.length > 0) {
|
|
41
|
+
log("hid-listen", "New device found:", newDevices);
|
|
42
|
+
|
|
43
|
+
listDevices = getDevices();
|
|
44
|
+
events.emit("add", getDeviceByPaths(newDevices));
|
|
45
|
+
|
|
46
|
+
changeFound = true;
|
|
47
|
+
} else {
|
|
48
|
+
log("hid-listen", "No new device found");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const removeDevices = lastDevices.filter(
|
|
52
|
+
(d) => !currentDevices.includes(d)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (removeDevices.length > 0) {
|
|
56
|
+
log("hid-listen", "Removed device found:", removeDevices);
|
|
57
|
+
|
|
58
|
+
events.emit("remove", getDeviceByPaths(removeDevices));
|
|
59
|
+
listDevices = listDevices.filter(
|
|
60
|
+
(d) => !removeDevices.includes(flatDevice(d))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
changeFound = true;
|
|
64
|
+
} else {
|
|
65
|
+
log("hid-listen", "No removed device found");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (changeFound) {
|
|
69
|
+
lastDevices = currentDevices;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
log("hid-listen", "Polling skipped, re-debouncing");
|
|
73
|
+
debouncedPoll();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const debouncedPoll = debounce(poll, delay);
|
|
78
|
+
|
|
79
|
+
const attachDetected = (device) => {
|
|
80
|
+
log("hid-listen", "Device add detected:", device);
|
|
81
|
+
|
|
82
|
+
debouncedPoll();
|
|
83
|
+
};
|
|
84
|
+
usb.on("attach", attachDetected);
|
|
85
|
+
log("hid-listen", "attach listener added");
|
|
86
|
+
|
|
87
|
+
const detachDetected = (device) => {
|
|
88
|
+
log("hid-listen", "Device removal detected:", device);
|
|
89
|
+
|
|
90
|
+
debouncedPoll();
|
|
91
|
+
};
|
|
92
|
+
usb.on("detach", detachDetected);
|
|
93
|
+
log("hid-listen", "detach listener added");
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
stop: () => {
|
|
97
|
+
log(
|
|
98
|
+
"hid-listen",
|
|
99
|
+
"Stop received, removing listeners and cancelling pending debounced polls"
|
|
100
|
+
);
|
|
101
|
+
debouncedPoll.cancel();
|
|
102
|
+
usb.removeListener("attach", attachDetected);
|
|
103
|
+
usb.removeListener("detach", detachDetected);
|
|
104
|
+
},
|
|
105
|
+
events,
|
|
106
|
+
};
|
|
107
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/hw-transport-node-hid",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.25.1-alpha.3+eb669e17",
|
|
4
4
|
"description": "Ledger Hardware Wallet Node implementation of the communication layer, using node-hid",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ledger",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"types": "lib/TransportNodeHid.d.ts",
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@ledgerhq/devices": "^6.
|
|
30
|
+
"@ledgerhq/devices": "^6.24.1",
|
|
31
31
|
"@ledgerhq/errors": "^6.10.0",
|
|
32
|
-
"@ledgerhq/hw-transport": "^6.
|
|
33
|
-
"@ledgerhq/hw-transport-node-hid-noevents": "^6.
|
|
32
|
+
"@ledgerhq/hw-transport": "^6.25.1-alpha.3+eb669e17",
|
|
33
|
+
"@ledgerhq/hw-transport-node-hid-noevents": "^6.25.1-alpha.3+eb669e17",
|
|
34
34
|
"@ledgerhq/logs": "^6.10.0",
|
|
35
35
|
"lodash": "^4.17.21",
|
|
36
36
|
"node-hid": "2.1.1",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"watch": "bash ../../script/watch.sh",
|
|
43
43
|
"doc": "bash ../../script/doc.sh"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "eb669e17dd87d3ab568beab1f9a5ddb1a2536e83"
|
|
46
46
|
}
|