@ledgerhq/hw-transport-node-hid-singleton 6.30.2 → 6.30.3-start-exchange.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/CHANGELOG.md +14 -0
- package/README.md +28 -6
- package/lib/TransportNodeHid.d.ts +19 -8
- package/lib/TransportNodeHid.d.ts.map +1 -1
- package/lib/TransportNodeHid.js +102 -83
- package/lib/TransportNodeHid.js.map +1 -1
- package/lib-es/TransportNodeHid.d.ts +19 -8
- package/lib-es/TransportNodeHid.d.ts.map +1 -1
- package/lib-es/TransportNodeHid.js +103 -84
- package/lib-es/TransportNodeHid.js.map +1 -1
- package/package.json +3 -3
- package/src/TransportNodeHid.ts +122 -76
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ledgerhq/hw-transport-node-hid-singleton
|
|
2
2
|
|
|
3
|
+
## 6.30.3-start-exchange.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5749](https://github.com/LedgerHQ/ledger-live/pull/5749) [`eadebff`](https://github.com/LedgerHQ/ledger-live/commit/eadebff3fe58aef6a5befb033d5147afc49663d3) Thanks [@alexandremgo](https://github.com/alexandremgo)! - Fix: HID USB reconnection on LLD during the sync onboarding
|
|
8
|
+
|
|
9
|
+
- Refactoring of the disconnect after inactivity of the transport implementation
|
|
10
|
+
hw-transport-node-hid-singleton
|
|
11
|
+
- Better logs and documentation
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`eadebff`](https://github.com/LedgerHQ/ledger-live/commit/eadebff3fe58aef6a5befb033d5147afc49663d3)]:
|
|
14
|
+
- @ledgerhq/hw-transport-node-hid-noevents@6.29.3-start-exchange.0
|
|
15
|
+
- @ledgerhq/hw-transport@6.30.3-start-exchange.0
|
|
16
|
+
|
|
3
17
|
## 6.30.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -34,14 +34,16 @@ For a smooth and quick integration:
|
|
|
34
34
|
* [Examples](#examples)
|
|
35
35
|
* [exchange](#exchange)
|
|
36
36
|
* [Parameters](#parameters-1)
|
|
37
|
+
* [close](#close)
|
|
37
38
|
* [isSupported](#issupported)
|
|
38
39
|
* [list](#list)
|
|
39
40
|
* [listen](#listen)
|
|
40
41
|
* [Parameters](#parameters-2)
|
|
41
|
-
* [
|
|
42
|
+
* [setDisconnectAfterInactivityTimeout](#setdisconnectafterinactivitytimeout)
|
|
42
43
|
* [disconnect](#disconnect)
|
|
43
44
|
* [open](#open)
|
|
44
45
|
* [Parameters](#parameters-3)
|
|
46
|
+
* [onDisconnect](#ondisconnect)
|
|
45
47
|
|
|
46
48
|
### TransportNodeHidSingleton
|
|
47
49
|
|
|
@@ -66,7 +68,7 @@ TransportNodeHid.create().then(transport => ...)
|
|
|
66
68
|
|
|
67
69
|
#### exchange
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
Exchanges with the device using APDU protocol
|
|
70
72
|
|
|
71
73
|
##### Parameters
|
|
72
74
|
|
|
@@ -74,6 +76,15 @@ Exchange with the device using APDU protocol.
|
|
|
74
76
|
|
|
75
77
|
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Buffer](https://nodejs.org/api/buffer.html)>** a promise of apdu response
|
|
76
78
|
|
|
79
|
+
#### close
|
|
80
|
+
|
|
81
|
+
Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
82
|
+
|
|
83
|
+
Intentionally not disconnecting the device/closing the hid connection directly:
|
|
84
|
+
The HID connection will only be closed after some inactivity.
|
|
85
|
+
|
|
86
|
+
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<void>** 
|
|
87
|
+
|
|
77
88
|
#### isSupported
|
|
78
89
|
|
|
79
90
|
#### list
|
|
@@ -86,15 +97,18 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
|
|
|
86
97
|
|
|
87
98
|
Returns **Subscription** 
|
|
88
99
|
|
|
89
|
-
####
|
|
100
|
+
#### setDisconnectAfterInactivityTimeout
|
|
90
101
|
|
|
91
|
-
|
|
102
|
+
Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
92
103
|
|
|
93
|
-
|
|
104
|
+
Currently, there is only one transport instance (for only one device connected via USB).
|
|
94
105
|
|
|
95
106
|
#### disconnect
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
Disconnects from the HID device associated to the transport singleton.
|
|
109
|
+
|
|
110
|
+
If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
111
|
+
the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
98
112
|
|
|
99
113
|
#### open
|
|
100
114
|
|
|
@@ -112,3 +126,11 @@ Legacy: `_descriptor` is needed to follow the Transport definition
|
|
|
112
126
|
* `context` **TraceContext?** 
|
|
113
127
|
|
|
114
128
|
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[TransportNodeHidSingleton](#transportnodehidsingleton)>** 
|
|
129
|
+
|
|
130
|
+
### onDisconnect
|
|
131
|
+
|
|
132
|
+
Disconnect event received from the transport instance.
|
|
133
|
+
|
|
134
|
+
It could be after a disconnection coming from the HID library (e.g. device unplugged) or from the transport instance itself (e.g. close).
|
|
135
|
+
Clearing the singleton instance.
|
|
136
|
+
Currently, only 1 device at a time is supported.
|
|
@@ -3,7 +3,7 @@ import HID from "node-hid";
|
|
|
3
3
|
import TransportNodeHidNoEvents from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
4
4
|
import type { Observer, DescriptorEvent, Subscription } from "@ledgerhq/hw-transport";
|
|
5
5
|
import { TraceContext } from "@ledgerhq/logs";
|
|
6
|
-
export type ListenDescriptorEvent = DescriptorEvent<
|
|
6
|
+
export type ListenDescriptorEvent = DescriptorEvent<string>;
|
|
7
7
|
/**
|
|
8
8
|
* node-hid Transport implementation
|
|
9
9
|
* @example
|
|
@@ -12,7 +12,6 @@ export type ListenDescriptorEvent = DescriptorEvent<any>;
|
|
|
12
12
|
* TransportNodeHid.create().then(transport => ...)
|
|
13
13
|
*/
|
|
14
14
|
export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents {
|
|
15
|
-
preventAutoDisconnect: boolean;
|
|
16
15
|
constructor(device: HID.HID, { context }?: {
|
|
17
16
|
context?: TraceContext;
|
|
18
17
|
});
|
|
@@ -27,14 +26,21 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
27
26
|
/**
|
|
28
27
|
*/
|
|
29
28
|
static listen: (observer: Observer<ListenDescriptorEvent>) => Subscription;
|
|
29
|
+
static disconnectAfterInactivityTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
static clearDisconnectAfterInactivityTimeout(): void;
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
+
* Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
33
|
+
*
|
|
34
|
+
* Currently, there is only one transport instance (for only one device connected via USB).
|
|
32
35
|
*/
|
|
33
|
-
static
|
|
36
|
+
static setDisconnectAfterInactivityTimeout(): void;
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
38
|
+
* Disconnects from the HID device associated to the transport singleton.
|
|
39
|
+
*
|
|
40
|
+
* If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
41
|
+
* the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
36
42
|
*/
|
|
37
|
-
static disconnect():
|
|
43
|
+
static disconnect(): void;
|
|
38
44
|
/**
|
|
39
45
|
* Connects to the first Ledger device connected via USB
|
|
40
46
|
*
|
|
@@ -44,14 +50,19 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
44
50
|
* Legacy: `_descriptor` is needed to follow the Transport definition
|
|
45
51
|
*/
|
|
46
52
|
static open(_descriptor: string, _timeoutMs?: number, context?: TraceContext): Promise<TransportNodeHidSingleton>;
|
|
47
|
-
setAllowAutoDisconnect(allow: boolean): void;
|
|
48
53
|
/**
|
|
49
|
-
*
|
|
54
|
+
* Exchanges with the device using APDU protocol
|
|
50
55
|
*
|
|
51
56
|
* @param apdu
|
|
52
57
|
* @returns a promise of apdu response
|
|
53
58
|
*/
|
|
54
59
|
exchange(apdu: Buffer): Promise<Buffer>;
|
|
60
|
+
/**
|
|
61
|
+
* Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
62
|
+
*
|
|
63
|
+
* Intentionally not disconnecting the device/closing the hid connection directly:
|
|
64
|
+
* The HID connection will only be closed after some inactivity.
|
|
65
|
+
*/
|
|
55
66
|
close(): Promise<void>;
|
|
56
67
|
}
|
|
57
68
|
//# sourceMappingURL=TransportNodeHid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransportNodeHid.d.ts","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":";AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,wBAAwC,MAAM,0CAA0C,CAAC;AAChG,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAe,YAAY,
|
|
1
|
+
{"version":3,"file":"TransportNodeHid.d.ts","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":";AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,wBAAwC,MAAM,0CAA0C,CAAC;AAChG,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAe,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAWlE,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,wBAAwB;gBACjE,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,GAAE;QAAE,OAAO,CAAC,EAAE,YAAY,CAAA;KAAO;IAIzE;;OAEG;IACH,MAAM,CAAC,WAAW,yBAAwC;IAE1D;;OAEG;IACH,MAAM,CAAC,IAAI,qBAAiC;IAE5C;OACG;IACH,MAAM,CAAC,MAAM,aAAc,SAAS,qBAAqB,CAAC,KAAG,YAAY,CAqDvE;IAEF,MAAM,CAAC,gCAAgC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,SAAS,CAAC;IACnF,MAAM,CAAC,qCAAqC;IAM5C;;;;OAIG;IACH,MAAM,CAAC,mCAAmC;IAiB1C;;;;;OAKG;IACH,MAAM,CAAC,UAAU;IAsBjB;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,yBAAyB,CAAC;IAkErC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvC;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAavB"}
|
package/lib/TransportNodeHid.js
CHANGED
|
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
27
|
};
|
|
@@ -43,17 +34,7 @@ const errors_1 = require("@ledgerhq/errors");
|
|
|
43
34
|
const listenDevices_1 = require("./listenDevices");
|
|
44
35
|
const LOG_TYPE = "hid-verbose";
|
|
45
36
|
let transportInstance = null;
|
|
46
|
-
const
|
|
47
|
-
let disconnectTimeout;
|
|
48
|
-
const clearDisconnectTimeout = () => {
|
|
49
|
-
if (disconnectTimeout) {
|
|
50
|
-
clearTimeout(disconnectTimeout);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const setDisconnectTimeout = () => {
|
|
54
|
-
clearDisconnectTimeout();
|
|
55
|
-
disconnectTimeout = setTimeout(() => TransportNodeHidSingleton.autoDisconnect(), DISCONNECT_TIMEOUT);
|
|
56
|
-
};
|
|
37
|
+
const DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS = 5000;
|
|
57
38
|
/**
|
|
58
39
|
* node-hid Transport implementation
|
|
59
40
|
* @example
|
|
@@ -64,36 +45,56 @@ const setDisconnectTimeout = () => {
|
|
|
64
45
|
class TransportNodeHidSingleton extends hw_transport_node_hid_noevents_1.default {
|
|
65
46
|
constructor(device, { context } = {}) {
|
|
66
47
|
super(device, { context, logType: LOG_TYPE });
|
|
67
|
-
|
|
48
|
+
}
|
|
49
|
+
static clearDisconnectAfterInactivityTimeout() {
|
|
50
|
+
if (TransportNodeHidSingleton.disconnectAfterInactivityTimeout) {
|
|
51
|
+
clearTimeout(TransportNodeHidSingleton.disconnectAfterInactivityTimeout);
|
|
52
|
+
}
|
|
68
53
|
}
|
|
69
54
|
/**
|
|
70
|
-
*
|
|
55
|
+
* Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
56
|
+
*
|
|
57
|
+
* Currently, there is only one transport instance (for only one device connected via USB).
|
|
71
58
|
*/
|
|
72
|
-
static
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
static setDisconnectAfterInactivityTimeout() {
|
|
60
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
61
|
+
TransportNodeHidSingleton.disconnectAfterInactivityTimeout = setTimeout(() => {
|
|
62
|
+
(0, logs_1.trace)({
|
|
63
|
+
type: LOG_TYPE,
|
|
64
|
+
message: "Disconnecting after inactivity, if not prevented",
|
|
65
|
+
data: {
|
|
66
|
+
hasInstance: Boolean(transportInstance),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (transportInstance) {
|
|
76
70
|
TransportNodeHidSingleton.disconnect();
|
|
77
71
|
}
|
|
78
|
-
|
|
79
|
-
// If we have disabled the auto-disconnect, try again in DISCONNECT_TIMEOUT
|
|
80
|
-
clearDisconnectTimeout();
|
|
81
|
-
setDisconnectTimeout();
|
|
82
|
-
}
|
|
83
|
-
});
|
|
72
|
+
}, DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS);
|
|
84
73
|
}
|
|
85
74
|
/**
|
|
86
|
-
*
|
|
75
|
+
* Disconnects from the HID device associated to the transport singleton.
|
|
76
|
+
*
|
|
77
|
+
* If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
78
|
+
* the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
87
79
|
*/
|
|
88
80
|
static disconnect() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
transportInstance
|
|
94
|
-
}
|
|
95
|
-
clearDisconnectTimeout();
|
|
81
|
+
(0, logs_1.trace)({
|
|
82
|
+
type: LOG_TYPE,
|
|
83
|
+
message: "Disconnecting from HID device",
|
|
84
|
+
data: {
|
|
85
|
+
hasInstance: Boolean(transportInstance),
|
|
86
|
+
},
|
|
96
87
|
});
|
|
88
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
89
|
+
if (transportInstance) {
|
|
90
|
+
transportInstance.device.close();
|
|
91
|
+
(0, logs_1.trace)({
|
|
92
|
+
type: LOG_TYPE,
|
|
93
|
+
message: `Closed HID communication with device. Emitting "disconnect" event from static disconnect and clearing singleton instance`,
|
|
94
|
+
});
|
|
95
|
+
transportInstance.emit("disconnect");
|
|
96
|
+
transportInstance = null;
|
|
97
|
+
}
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
100
|
* Connects to the first Ledger device connected via USB
|
|
@@ -105,61 +106,79 @@ class TransportNodeHidSingleton extends hw_transport_node_hid_noevents_1.default
|
|
|
105
106
|
*/
|
|
106
107
|
static open(_descriptor, _timeoutMs, context) {
|
|
107
108
|
const tracer = new logs_1.LocalTracer(LOG_TYPE, context);
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
110
|
+
if (transportInstance) {
|
|
111
|
+
tracer.trace("Reusing already opened transport instance");
|
|
112
|
+
return Promise.resolve(transportInstance);
|
|
113
|
+
}
|
|
114
|
+
const devicesDetectedDuringOpen = (0, hw_transport_node_hid_noevents_1.getDevices)();
|
|
115
|
+
tracer.trace(`Devices detected during open: ${devicesDetectedDuringOpen.length}`, {
|
|
116
|
+
devicesDetectedDuringOpen,
|
|
117
|
+
});
|
|
118
|
+
if (devicesDetectedDuringOpen.length === 0) {
|
|
119
|
+
return Promise.reject(new errors_1.CantOpenDevice("No device found"));
|
|
120
|
+
}
|
|
121
|
+
const device = devicesDetectedDuringOpen[0];
|
|
122
|
+
tracer.trace("Found a device, creating HID transport instance ...", { device });
|
|
123
|
+
let HIDDevice;
|
|
124
|
+
try {
|
|
125
|
+
HIDDevice = new node_hid_1.default.HID(device.path);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
tracer.trace(`Error while connecting to device: ${error}`, { error });
|
|
129
|
+
return Promise.reject(error);
|
|
130
|
+
}
|
|
131
|
+
transportInstance = new TransportNodeHidSingleton(HIDDevice, {
|
|
132
|
+
context,
|
|
133
|
+
});
|
|
134
|
+
const clearDeviceEventsListener = (0, listenDevices_1.listenDevices)(() => { }, () => {
|
|
135
|
+
// Assumes any ledger disconnection concerns current transport
|
|
110
136
|
if (transportInstance) {
|
|
111
|
-
tracer.trace("
|
|
112
|
-
|
|
137
|
+
tracer.trace("Listened to on remove device event. Emitting a disconnect");
|
|
138
|
+
transportInstance.emit("disconnect");
|
|
113
139
|
}
|
|
114
|
-
const device = (0, hw_transport_node_hid_noevents_1.getDevices)()[0];
|
|
115
|
-
if (!device)
|
|
116
|
-
throw new errors_1.CantOpenDevice("no device found");
|
|
117
|
-
tracer.trace("Found a device, creating HID transport instance ...", { device });
|
|
118
|
-
transportInstance = new TransportNodeHidSingleton(new node_hid_1.default.HID(device.path), {
|
|
119
|
-
context,
|
|
120
|
-
});
|
|
121
|
-
const unlisten = (0, listenDevices_1.listenDevices)(() => { }, () => {
|
|
122
|
-
// Assumes any ledger disconnection concerns current transport
|
|
123
|
-
if (transportInstance) {
|
|
124
|
-
transportInstance.emit("disconnect");
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
const onDisconnect = () => {
|
|
128
|
-
if (!transportInstance)
|
|
129
|
-
return;
|
|
130
|
-
tracer.trace("Device was disconnected, clearing transport instance ...");
|
|
131
|
-
transportInstance.off("disconnect", onDisconnect);
|
|
132
|
-
transportInstance = null;
|
|
133
|
-
unlisten();
|
|
134
|
-
};
|
|
135
|
-
transportInstance.on("disconnect", onDisconnect);
|
|
136
|
-
return transportInstance;
|
|
137
140
|
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Disconnect event received from the transport instance.
|
|
143
|
+
*
|
|
144
|
+
* It could be after a disconnection coming from the HID library (e.g. device unplugged) or from the transport instance itself (e.g. close).
|
|
145
|
+
* Clearing the singleton instance.
|
|
146
|
+
* Currently, only 1 device at a time is supported.
|
|
147
|
+
*/
|
|
148
|
+
const onDisconnect = () => {
|
|
149
|
+
if (!transportInstance) {
|
|
150
|
+
tracer.trace("disconnect event without transport instance, ignoring ...");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.clearDisconnectAfterInactivityTimeout();
|
|
154
|
+
transportInstance.off("disconnect", onDisconnect);
|
|
155
|
+
transportInstance = null;
|
|
156
|
+
clearDeviceEventsListener();
|
|
157
|
+
};
|
|
158
|
+
transportInstance.on("disconnect", onDisconnect);
|
|
159
|
+
return Promise.resolve(transportInstance);
|
|
141
160
|
}
|
|
142
161
|
/**
|
|
143
|
-
*
|
|
162
|
+
* Exchanges with the device using APDU protocol
|
|
144
163
|
*
|
|
145
164
|
* @param apdu
|
|
146
165
|
* @returns a promise of apdu response
|
|
147
166
|
*/
|
|
148
167
|
exchange(apdu) {
|
|
149
|
-
|
|
150
|
-
exchange: { get: () => super.exchange }
|
|
151
|
-
});
|
|
152
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
clearDisconnectTimeout();
|
|
154
|
-
const result = yield _super.exchange.call(this, apdu);
|
|
155
|
-
setDisconnectTimeout();
|
|
156
|
-
return result;
|
|
157
|
-
});
|
|
168
|
+
return super.exchange(apdu);
|
|
158
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
172
|
+
*
|
|
173
|
+
* Intentionally not disconnecting the device/closing the hid connection directly:
|
|
174
|
+
* The HID connection will only be closed after some inactivity.
|
|
175
|
+
*/
|
|
159
176
|
close() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
this.tracer.trace("Closing transport instance by triggering a disconnection after some inactivity", {
|
|
178
|
+
hasInstance: !!transportInstance,
|
|
179
|
+
disconnectAfterInactivityTimeoutMs: DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS,
|
|
180
|
+
});
|
|
181
|
+
TransportNodeHidSingleton.setDisconnectAfterInactivityTimeout();
|
|
163
182
|
return Promise.resolve();
|
|
164
183
|
}
|
|
165
184
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransportNodeHid.js","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TransportNodeHid.js","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAA2B;AAC3B,2GAAgG;AAEhG,yCAAkE;AAClE,+CAAyD;AACzD,6CAAkD;AAClD,mDAAgD;AAEhD,MAAM,QAAQ,GAAG,aAAa,CAAC;AAE/B,IAAI,iBAAiB,GAAqC,IAAI,CAAC;AAE/D,MAAM,sCAAsC,GAAG,IAAI,CAAC;AAIpD;;;;;;GAMG;AACH,MAAqB,yBAA0B,SAAQ,wCAAwB;IAC7E,YAAY,MAAe,EAAE,EAAE,OAAO,KAAiC,EAAE;QACvE,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChD,CAAC;IAsED,MAAM,CAAC,qCAAqC;QAC1C,IAAI,yBAAyB,CAAC,gCAAgC,EAAE;YAC9D,YAAY,CAAC,yBAAyB,CAAC,gCAAgC,CAAC,CAAC;SAC1E;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,mCAAmC;QACxC,yBAAyB,CAAC,qCAAqC,EAAE,CAAC;QAClE,yBAAyB,CAAC,gCAAgC,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3E,IAAA,YAAK,EAAC;gBACJ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,kDAAkD;gBAC3D,IAAI,EAAE;oBACJ,WAAW,EAAE,OAAO,CAAC,iBAAiB,CAAC;iBACxC;aACF,CAAC,CAAC;YAEH,IAAI,iBAAiB,EAAE;gBACrB,yBAAyB,CAAC,UAAU,EAAE,CAAC;aACxC;QACH,CAAC,EAAE,sCAAsC,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,UAAU;QACf,IAAA,YAAK,EAAC;YACJ,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,+BAA+B;YACxC,IAAI,EAAE;gBACJ,WAAW,EAAE,OAAO,CAAC,iBAAiB,CAAC;aACxC;SACF,CAAC,CAAC;QAEH,yBAAyB,CAAC,qCAAqC,EAAE,CAAC;QAElE,IAAI,iBAAiB,EAAE;YACrB,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,IAAA,YAAK,EAAC;gBACJ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,0HAA0H;aACpI,CAAC,CAAC;YACH,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,iBAAiB,GAAG,IAAI,CAAC;SAC1B;IACH,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,WAAmB,EACnB,UAAmB,EACnB,OAAsB;QAEtB,MAAM,MAAM,GAAG,IAAI,kBAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,yBAAyB,CAAC,qCAAqC,EAAE,CAAC;QAElE,IAAI,iBAAiB,EAAE;YACrB,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC1D,OAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;SAC3C;QAED,MAAM,yBAAyB,GAAG,IAAA,2CAAU,GAAE,CAAC;QAC/C,MAAM,CAAC,KAAK,CAAC,iCAAiC,yBAAyB,CAAC,MAAM,EAAE,EAAE;YAChF,yBAAyB;SAC1B,CAAC,CAAC;QAEH,IAAI,yBAAyB,CAAC,MAAM,KAAK,CAAC,EAAE;YAC1C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,uBAAc,CAAC,iBAAiB,CAAC,CAAC,CAAC;SAC9D;QACD,MAAM,MAAM,GAAG,yBAAyB,CAAC,CAAC,CAAC,CAAC;QAE5C,MAAM,CAAC,KAAK,CAAC,qDAAqD,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEhF,IAAI,SAA0B,CAAC;QAC/B,IAAI;YACF,SAAS,GAAG,IAAI,kBAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAc,CAAC,CAAC;SAChD;QAAC,OAAO,KAAK,EAAE;YACd,MAAM,CAAC,KAAK,CAAC,qCAAqC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SAC9B;QAED,iBAAiB,GAAG,IAAI,yBAAyB,CAAC,SAAS,EAAE;YAC3D,OAAO;SACR,CAAC,CAAC;QAEH,MAAM,yBAAyB,GAAG,IAAA,6BAAa,EAC7C,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE;YACH,8DAA8D;YAC9D,IAAI,iBAAiB,EAAE;gBACrB,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;gBAC1E,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;aACtC;QACH,CAAC,CACF,CAAC;QAEF;;;;;;WAMG;QACH,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,IAAI,CAAC,iBAAiB,EAAE;gBACtB,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;gBAC1E,OAAO;aACR;YACD,IAAI,CAAC,qCAAqC,EAAE,CAAC;YAC7C,iBAAiB,CAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YAClD,iBAAiB,GAAG,IAAI,CAAC;YACzB,yBAAyB,EAAE,CAAC;QAC9B,CAAC,CAAC;QAEF,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QACjD,OAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,IAAY;QACnB,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,gFAAgF,EAChF;YACE,WAAW,EAAE,CAAC,CAAC,iBAAiB;YAChC,kCAAkC,EAAE,sCAAsC;SAC3E,CACF,CAAC;QAEF,yBAAyB,CAAC,mCAAmC,EAAE,CAAC;QAEhE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;;AAtOD;;GAEG;AACI,qCAAW,GAAG,wCAAwB,CAAC,WAAW,CAAC;AAE1D;;GAEG;AACI,8BAAI,GAAG,wCAAwB,CAAC,IAAI,CAAC;AAE5C;GACG;AACI,gCAAM,GAAG,CAAC,QAAyC,EAAgB,EAAE;IAC1E,IAAI,YAAqB,CAAC;IAC1B,OAAO,CAAC,OAAO,CAAC,IAAA,2CAAU,GAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QAC3C,oFAAoF;QACpF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;YAC5B,IAAI,CAAC,YAAY,EAAE;gBACjB,MAAM,WAAW,GAAG,IAAA,8BAAoB,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC3D,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,KAAK;oBACX,UAAU,EAAE,EAAE;oBACd,MAAM,EAAE;wBACN,IAAI,EAAE,MAAM,CAAC,UAAU;qBACxB;oBACD,WAAW;iBACZ,CAAC,CAAC;aACJ;SACF;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE;QACrB,MAAM,WAAW,GAAG,IAAA,8BAAoB,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3D,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,EAAE;YACd,WAAW;YACX,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM,CAAC,UAAU;aACxB;SACF,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE;QACxB,MAAM,WAAW,GAAG,IAAA,8BAAoB,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3D,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,EAAE;YACd,WAAW;YACX,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM,CAAC,UAAU;aACxB;SACF,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAA,6BAAa,EAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE5C,SAAS,WAAW;QAClB,IAAI,EAAE,CAAC;QACP,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,OAAO;QACL,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC;kBAtEiB,yBAAyB"}
|
|
@@ -3,7 +3,7 @@ import HID from "node-hid";
|
|
|
3
3
|
import TransportNodeHidNoEvents from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
4
4
|
import type { Observer, DescriptorEvent, Subscription } from "@ledgerhq/hw-transport";
|
|
5
5
|
import { TraceContext } from "@ledgerhq/logs";
|
|
6
|
-
export type ListenDescriptorEvent = DescriptorEvent<
|
|
6
|
+
export type ListenDescriptorEvent = DescriptorEvent<string>;
|
|
7
7
|
/**
|
|
8
8
|
* node-hid Transport implementation
|
|
9
9
|
* @example
|
|
@@ -12,7 +12,6 @@ export type ListenDescriptorEvent = DescriptorEvent<any>;
|
|
|
12
12
|
* TransportNodeHid.create().then(transport => ...)
|
|
13
13
|
*/
|
|
14
14
|
export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents {
|
|
15
|
-
preventAutoDisconnect: boolean;
|
|
16
15
|
constructor(device: HID.HID, { context }?: {
|
|
17
16
|
context?: TraceContext;
|
|
18
17
|
});
|
|
@@ -27,14 +26,21 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
27
26
|
/**
|
|
28
27
|
*/
|
|
29
28
|
static listen: (observer: Observer<ListenDescriptorEvent>) => Subscription;
|
|
29
|
+
static disconnectAfterInactivityTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
static clearDisconnectAfterInactivityTimeout(): void;
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
+
* Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
33
|
+
*
|
|
34
|
+
* Currently, there is only one transport instance (for only one device connected via USB).
|
|
32
35
|
*/
|
|
33
|
-
static
|
|
36
|
+
static setDisconnectAfterInactivityTimeout(): void;
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
38
|
+
* Disconnects from the HID device associated to the transport singleton.
|
|
39
|
+
*
|
|
40
|
+
* If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
41
|
+
* the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
36
42
|
*/
|
|
37
|
-
static disconnect():
|
|
43
|
+
static disconnect(): void;
|
|
38
44
|
/**
|
|
39
45
|
* Connects to the first Ledger device connected via USB
|
|
40
46
|
*
|
|
@@ -44,14 +50,19 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
44
50
|
* Legacy: `_descriptor` is needed to follow the Transport definition
|
|
45
51
|
*/
|
|
46
52
|
static open(_descriptor: string, _timeoutMs?: number, context?: TraceContext): Promise<TransportNodeHidSingleton>;
|
|
47
|
-
setAllowAutoDisconnect(allow: boolean): void;
|
|
48
53
|
/**
|
|
49
|
-
*
|
|
54
|
+
* Exchanges with the device using APDU protocol
|
|
50
55
|
*
|
|
51
56
|
* @param apdu
|
|
52
57
|
* @returns a promise of apdu response
|
|
53
58
|
*/
|
|
54
59
|
exchange(apdu: Buffer): Promise<Buffer>;
|
|
60
|
+
/**
|
|
61
|
+
* Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
62
|
+
*
|
|
63
|
+
* Intentionally not disconnecting the device/closing the hid connection directly:
|
|
64
|
+
* The HID connection will only be closed after some inactivity.
|
|
65
|
+
*/
|
|
55
66
|
close(): Promise<void>;
|
|
56
67
|
}
|
|
57
68
|
//# sourceMappingURL=TransportNodeHid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransportNodeHid.d.ts","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":";AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,wBAAwC,MAAM,0CAA0C,CAAC;AAChG,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAe,YAAY,
|
|
1
|
+
{"version":3,"file":"TransportNodeHid.d.ts","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":";AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,wBAAwC,MAAM,0CAA0C,CAAC;AAChG,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAe,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAWlE,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,wBAAwB;gBACjE,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,GAAE;QAAE,OAAO,CAAC,EAAE,YAAY,CAAA;KAAO;IAIzE;;OAEG;IACH,MAAM,CAAC,WAAW,yBAAwC;IAE1D;;OAEG;IACH,MAAM,CAAC,IAAI,qBAAiC;IAE5C;OACG;IACH,MAAM,CAAC,MAAM,aAAc,SAAS,qBAAqB,CAAC,KAAG,YAAY,CAqDvE;IAEF,MAAM,CAAC,gCAAgC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,SAAS,CAAC;IACnF,MAAM,CAAC,qCAAqC;IAM5C;;;;OAIG;IACH,MAAM,CAAC,mCAAmC;IAiB1C;;;;;OAKG;IACH,MAAM,CAAC,UAAU;IAsBjB;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CACT,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,yBAAyB,CAAC;IAkErC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvC;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAavB"}
|
|
@@ -1,31 +1,12 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import HID from "node-hid";
|
|
11
2
|
import TransportNodeHidNoEvents, { getDevices } from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
12
|
-
import { LocalTracer,
|
|
3
|
+
import { LocalTracer, trace } from "@ledgerhq/logs";
|
|
13
4
|
import { identifyUSBProductId } from "@ledgerhq/devices";
|
|
14
5
|
import { CantOpenDevice } from "@ledgerhq/errors";
|
|
15
6
|
import { listenDevices } from "./listenDevices";
|
|
16
7
|
const LOG_TYPE = "hid-verbose";
|
|
17
8
|
let transportInstance = null;
|
|
18
|
-
const
|
|
19
|
-
let disconnectTimeout;
|
|
20
|
-
const clearDisconnectTimeout = () => {
|
|
21
|
-
if (disconnectTimeout) {
|
|
22
|
-
clearTimeout(disconnectTimeout);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
const setDisconnectTimeout = () => {
|
|
26
|
-
clearDisconnectTimeout();
|
|
27
|
-
disconnectTimeout = setTimeout(() => TransportNodeHidSingleton.autoDisconnect(), DISCONNECT_TIMEOUT);
|
|
28
|
-
};
|
|
9
|
+
const DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS = 5000;
|
|
29
10
|
/**
|
|
30
11
|
* node-hid Transport implementation
|
|
31
12
|
* @example
|
|
@@ -36,36 +17,56 @@ const setDisconnectTimeout = () => {
|
|
|
36
17
|
class TransportNodeHidSingleton extends TransportNodeHidNoEvents {
|
|
37
18
|
constructor(device, { context } = {}) {
|
|
38
19
|
super(device, { context, logType: LOG_TYPE });
|
|
39
|
-
|
|
20
|
+
}
|
|
21
|
+
static clearDisconnectAfterInactivityTimeout() {
|
|
22
|
+
if (TransportNodeHidSingleton.disconnectAfterInactivityTimeout) {
|
|
23
|
+
clearTimeout(TransportNodeHidSingleton.disconnectAfterInactivityTimeout);
|
|
24
|
+
}
|
|
40
25
|
}
|
|
41
26
|
/**
|
|
42
|
-
*
|
|
27
|
+
* Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
28
|
+
*
|
|
29
|
+
* Currently, there is only one transport instance (for only one device connected via USB).
|
|
43
30
|
*/
|
|
44
|
-
static
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
static setDisconnectAfterInactivityTimeout() {
|
|
32
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
33
|
+
TransportNodeHidSingleton.disconnectAfterInactivityTimeout = setTimeout(() => {
|
|
34
|
+
trace({
|
|
35
|
+
type: LOG_TYPE,
|
|
36
|
+
message: "Disconnecting after inactivity, if not prevented",
|
|
37
|
+
data: {
|
|
38
|
+
hasInstance: Boolean(transportInstance),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (transportInstance) {
|
|
48
42
|
TransportNodeHidSingleton.disconnect();
|
|
49
43
|
}
|
|
50
|
-
|
|
51
|
-
// If we have disabled the auto-disconnect, try again in DISCONNECT_TIMEOUT
|
|
52
|
-
clearDisconnectTimeout();
|
|
53
|
-
setDisconnectTimeout();
|
|
54
|
-
}
|
|
55
|
-
});
|
|
44
|
+
}, DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS);
|
|
56
45
|
}
|
|
57
46
|
/**
|
|
58
|
-
*
|
|
47
|
+
* Disconnects from the HID device associated to the transport singleton.
|
|
48
|
+
*
|
|
49
|
+
* If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
50
|
+
* the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
59
51
|
*/
|
|
60
52
|
static disconnect() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
transportInstance
|
|
66
|
-
}
|
|
67
|
-
clearDisconnectTimeout();
|
|
53
|
+
trace({
|
|
54
|
+
type: LOG_TYPE,
|
|
55
|
+
message: "Disconnecting from HID device",
|
|
56
|
+
data: {
|
|
57
|
+
hasInstance: Boolean(transportInstance),
|
|
58
|
+
},
|
|
68
59
|
});
|
|
60
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
61
|
+
if (transportInstance) {
|
|
62
|
+
transportInstance.device.close();
|
|
63
|
+
trace({
|
|
64
|
+
type: LOG_TYPE,
|
|
65
|
+
message: `Closed HID communication with device. Emitting "disconnect" event from static disconnect and clearing singleton instance`,
|
|
66
|
+
});
|
|
67
|
+
transportInstance.emit("disconnect");
|
|
68
|
+
transportInstance = null;
|
|
69
|
+
}
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
72
|
* Connects to the first Ledger device connected via USB
|
|
@@ -77,61 +78,79 @@ class TransportNodeHidSingleton extends TransportNodeHidNoEvents {
|
|
|
77
78
|
*/
|
|
78
79
|
static open(_descriptor, _timeoutMs, context) {
|
|
79
80
|
const tracer = new LocalTracer(LOG_TYPE, context);
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
82
|
+
if (transportInstance) {
|
|
83
|
+
tracer.trace("Reusing already opened transport instance");
|
|
84
|
+
return Promise.resolve(transportInstance);
|
|
85
|
+
}
|
|
86
|
+
const devicesDetectedDuringOpen = getDevices();
|
|
87
|
+
tracer.trace(`Devices detected during open: ${devicesDetectedDuringOpen.length}`, {
|
|
88
|
+
devicesDetectedDuringOpen,
|
|
89
|
+
});
|
|
90
|
+
if (devicesDetectedDuringOpen.length === 0) {
|
|
91
|
+
return Promise.reject(new CantOpenDevice("No device found"));
|
|
92
|
+
}
|
|
93
|
+
const device = devicesDetectedDuringOpen[0];
|
|
94
|
+
tracer.trace("Found a device, creating HID transport instance ...", { device });
|
|
95
|
+
let HIDDevice;
|
|
96
|
+
try {
|
|
97
|
+
HIDDevice = new HID.HID(device.path);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
tracer.trace(`Error while connecting to device: ${error}`, { error });
|
|
101
|
+
return Promise.reject(error);
|
|
102
|
+
}
|
|
103
|
+
transportInstance = new TransportNodeHidSingleton(HIDDevice, {
|
|
104
|
+
context,
|
|
105
|
+
});
|
|
106
|
+
const clearDeviceEventsListener = listenDevices(() => { }, () => {
|
|
107
|
+
// Assumes any ledger disconnection concerns current transport
|
|
82
108
|
if (transportInstance) {
|
|
83
|
-
tracer.trace("
|
|
84
|
-
|
|
109
|
+
tracer.trace("Listened to on remove device event. Emitting a disconnect");
|
|
110
|
+
transportInstance.emit("disconnect");
|
|
85
111
|
}
|
|
86
|
-
const device = getDevices()[0];
|
|
87
|
-
if (!device)
|
|
88
|
-
throw new CantOpenDevice("no device found");
|
|
89
|
-
tracer.trace("Found a device, creating HID transport instance ...", { device });
|
|
90
|
-
transportInstance = new TransportNodeHidSingleton(new HID.HID(device.path), {
|
|
91
|
-
context,
|
|
92
|
-
});
|
|
93
|
-
const unlisten = listenDevices(() => { }, () => {
|
|
94
|
-
// Assumes any ledger disconnection concerns current transport
|
|
95
|
-
if (transportInstance) {
|
|
96
|
-
transportInstance.emit("disconnect");
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
const onDisconnect = () => {
|
|
100
|
-
if (!transportInstance)
|
|
101
|
-
return;
|
|
102
|
-
tracer.trace("Device was disconnected, clearing transport instance ...");
|
|
103
|
-
transportInstance.off("disconnect", onDisconnect);
|
|
104
|
-
transportInstance = null;
|
|
105
|
-
unlisten();
|
|
106
|
-
};
|
|
107
|
-
transportInstance.on("disconnect", onDisconnect);
|
|
108
|
-
return transportInstance;
|
|
109
112
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Disconnect event received from the transport instance.
|
|
115
|
+
*
|
|
116
|
+
* It could be after a disconnection coming from the HID library (e.g. device unplugged) or from the transport instance itself (e.g. close).
|
|
117
|
+
* Clearing the singleton instance.
|
|
118
|
+
* Currently, only 1 device at a time is supported.
|
|
119
|
+
*/
|
|
120
|
+
const onDisconnect = () => {
|
|
121
|
+
if (!transportInstance) {
|
|
122
|
+
tracer.trace("disconnect event without transport instance, ignoring ...");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.clearDisconnectAfterInactivityTimeout();
|
|
126
|
+
transportInstance.off("disconnect", onDisconnect);
|
|
127
|
+
transportInstance = null;
|
|
128
|
+
clearDeviceEventsListener();
|
|
129
|
+
};
|
|
130
|
+
transportInstance.on("disconnect", onDisconnect);
|
|
131
|
+
return Promise.resolve(transportInstance);
|
|
113
132
|
}
|
|
114
133
|
/**
|
|
115
|
-
*
|
|
134
|
+
* Exchanges with the device using APDU protocol
|
|
116
135
|
*
|
|
117
136
|
* @param apdu
|
|
118
137
|
* @returns a promise of apdu response
|
|
119
138
|
*/
|
|
120
139
|
exchange(apdu) {
|
|
121
|
-
|
|
122
|
-
exchange: { get: () => super.exchange }
|
|
123
|
-
});
|
|
124
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
-
clearDisconnectTimeout();
|
|
126
|
-
const result = yield _super.exchange.call(this, apdu);
|
|
127
|
-
setDisconnectTimeout();
|
|
128
|
-
return result;
|
|
129
|
-
});
|
|
140
|
+
return super.exchange(apdu);
|
|
130
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
144
|
+
*
|
|
145
|
+
* Intentionally not disconnecting the device/closing the hid connection directly:
|
|
146
|
+
* The HID connection will only be closed after some inactivity.
|
|
147
|
+
*/
|
|
131
148
|
close() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
149
|
+
this.tracer.trace("Closing transport instance by triggering a disconnection after some inactivity", {
|
|
150
|
+
hasInstance: !!transportInstance,
|
|
151
|
+
disconnectAfterInactivityTimeoutMs: DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS,
|
|
152
|
+
});
|
|
153
|
+
TransportNodeHidSingleton.setDisconnectAfterInactivityTimeout();
|
|
135
154
|
return Promise.resolve();
|
|
136
155
|
}
|
|
137
156
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransportNodeHid.js","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TransportNodeHid.js","sourceRoot":"","sources":["../src/TransportNodeHid.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/hw-transport-node-hid-singleton",
|
|
3
|
-
"version": "6.30.
|
|
3
|
+
"version": "6.30.3-start-exchange.0",
|
|
4
4
|
"description": "Ledger Hardware Wallet Node implementation of the communication layer, using node-hid and node-usb",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ledger",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"node-hid": "^2.1.2",
|
|
31
31
|
"usb": "2.9.0",
|
|
32
32
|
"@ledgerhq/devices": "^8.2.0",
|
|
33
|
-
"@ledgerhq/hw-transport": "^6.30.2",
|
|
34
33
|
"@ledgerhq/errors": "^6.16.1",
|
|
35
|
-
"@ledgerhq/hw-transport
|
|
34
|
+
"@ledgerhq/hw-transport": "^6.30.3-start-exchange.0",
|
|
35
|
+
"@ledgerhq/hw-transport-node-hid-noevents": "^6.29.3-start-exchange.0",
|
|
36
36
|
"@ledgerhq/logs": "^6.12.0"
|
|
37
37
|
},
|
|
38
38
|
"gitHead": "dd0dea64b58e5a9125c8a422dcffd29e5ef6abec",
|
package/src/TransportNodeHid.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import HID from "node-hid";
|
|
2
2
|
import TransportNodeHidNoEvents, { getDevices } from "@ledgerhq/hw-transport-node-hid-noevents";
|
|
3
3
|
import type { Observer, DescriptorEvent, Subscription } from "@ledgerhq/hw-transport";
|
|
4
|
-
import { LocalTracer, TraceContext,
|
|
4
|
+
import { LocalTracer, TraceContext, trace } from "@ledgerhq/logs";
|
|
5
5
|
import { identifyUSBProductId } from "@ledgerhq/devices";
|
|
6
6
|
import { CantOpenDevice } from "@ledgerhq/errors";
|
|
7
7
|
import { listenDevices } from "./listenDevices";
|
|
@@ -10,23 +10,9 @@ const LOG_TYPE = "hid-verbose";
|
|
|
10
10
|
|
|
11
11
|
let transportInstance: TransportNodeHidSingleton | null = null;
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
let disconnectTimeout;
|
|
15
|
-
const clearDisconnectTimeout = () => {
|
|
16
|
-
if (disconnectTimeout) {
|
|
17
|
-
clearTimeout(disconnectTimeout);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const setDisconnectTimeout = () => {
|
|
22
|
-
clearDisconnectTimeout();
|
|
23
|
-
disconnectTimeout = setTimeout(
|
|
24
|
-
() => TransportNodeHidSingleton.autoDisconnect(),
|
|
25
|
-
DISCONNECT_TIMEOUT,
|
|
26
|
-
);
|
|
27
|
-
};
|
|
13
|
+
const DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS = 5000;
|
|
28
14
|
|
|
29
|
-
export type ListenDescriptorEvent = DescriptorEvent<
|
|
15
|
+
export type ListenDescriptorEvent = DescriptorEvent<string>;
|
|
30
16
|
|
|
31
17
|
/**
|
|
32
18
|
* node-hid Transport implementation
|
|
@@ -36,8 +22,6 @@ export type ListenDescriptorEvent = DescriptorEvent<any>;
|
|
|
36
22
|
* TransportNodeHid.create().then(transport => ...)
|
|
37
23
|
*/
|
|
38
24
|
export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents {
|
|
39
|
-
preventAutoDisconnect = false;
|
|
40
|
-
|
|
41
25
|
constructor(device: HID.HID, { context }: { context?: TraceContext } = {}) {
|
|
42
26
|
super(device, { context, logType: LOG_TYPE });
|
|
43
27
|
}
|
|
@@ -109,30 +93,61 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
109
93
|
};
|
|
110
94
|
};
|
|
111
95
|
|
|
96
|
+
static disconnectAfterInactivityTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
97
|
+
static clearDisconnectAfterInactivityTimeout() {
|
|
98
|
+
if (TransportNodeHidSingleton.disconnectAfterInactivityTimeout) {
|
|
99
|
+
clearTimeout(TransportNodeHidSingleton.disconnectAfterInactivityTimeout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
112
103
|
/**
|
|
113
|
-
*
|
|
104
|
+
* Disconnects device from singleton instance after some inactivity (no new `open`).
|
|
105
|
+
*
|
|
106
|
+
* Currently, there is only one transport instance (for only one device connected via USB).
|
|
114
107
|
*/
|
|
115
|
-
static
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
static setDisconnectAfterInactivityTimeout() {
|
|
109
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
110
|
+
TransportNodeHidSingleton.disconnectAfterInactivityTimeout = setTimeout(() => {
|
|
111
|
+
trace({
|
|
112
|
+
type: LOG_TYPE,
|
|
113
|
+
message: "Disconnecting after inactivity, if not prevented",
|
|
114
|
+
data: {
|
|
115
|
+
hasInstance: Boolean(transportInstance),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (transportInstance) {
|
|
120
|
+
TransportNodeHidSingleton.disconnect();
|
|
121
|
+
}
|
|
122
|
+
}, DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS);
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
/**
|
|
127
|
-
*
|
|
126
|
+
* Disconnects from the HID device associated to the transport singleton.
|
|
127
|
+
*
|
|
128
|
+
* If you want to try to re-use the same transport instance at the next action (when calling `open` again), you can use
|
|
129
|
+
* the transport instance `close` method: it will only enable a disconnect after some inactivity.
|
|
128
130
|
*/
|
|
129
|
-
static
|
|
131
|
+
static disconnect() {
|
|
132
|
+
trace({
|
|
133
|
+
type: LOG_TYPE,
|
|
134
|
+
message: "Disconnecting from HID device",
|
|
135
|
+
data: {
|
|
136
|
+
hasInstance: Boolean(transportInstance),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
141
|
+
|
|
130
142
|
if (transportInstance) {
|
|
131
143
|
transportInstance.device.close();
|
|
144
|
+
trace({
|
|
145
|
+
type: LOG_TYPE,
|
|
146
|
+
message: `Closed HID communication with device. Emitting "disconnect" event from static disconnect and clearing singleton instance`,
|
|
147
|
+
});
|
|
132
148
|
transportInstance.emit("disconnect");
|
|
133
149
|
transportInstance = null;
|
|
134
150
|
}
|
|
135
|
-
clearDisconnectTimeout();
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
/**
|
|
@@ -149,66 +164,97 @@ export default class TransportNodeHidSingleton extends TransportNodeHidNoEvents
|
|
|
149
164
|
context?: TraceContext,
|
|
150
165
|
): Promise<TransportNodeHidSingleton> {
|
|
151
166
|
const tracer = new LocalTracer(LOG_TYPE, context);
|
|
152
|
-
|
|
167
|
+
TransportNodeHidSingleton.clearDisconnectAfterInactivityTimeout();
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
169
|
+
if (transportInstance) {
|
|
170
|
+
tracer.trace("Reusing already opened transport instance");
|
|
171
|
+
return Promise.resolve(transportInstance);
|
|
172
|
+
}
|
|
159
173
|
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
const devicesDetectedDuringOpen = getDevices();
|
|
175
|
+
tracer.trace(`Devices detected during open: ${devicesDetectedDuringOpen.length}`, {
|
|
176
|
+
devicesDetectedDuringOpen,
|
|
177
|
+
});
|
|
162
178
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
if (devicesDetectedDuringOpen.length === 0) {
|
|
180
|
+
return Promise.reject(new CantOpenDevice("No device found"));
|
|
181
|
+
}
|
|
182
|
+
const device = devicesDetectedDuringOpen[0];
|
|
167
183
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
tracer.trace("Device was disconnected, clearing transport instance ...");
|
|
181
|
-
transportInstance.off("disconnect", onDisconnect);
|
|
182
|
-
transportInstance = null;
|
|
183
|
-
unlisten();
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
transportInstance.on("disconnect", onDisconnect);
|
|
187
|
-
return transportInstance;
|
|
184
|
+
tracer.trace("Found a device, creating HID transport instance ...", { device });
|
|
185
|
+
|
|
186
|
+
let HIDDevice: HID | undefined;
|
|
187
|
+
try {
|
|
188
|
+
HIDDevice = new HID.HID(device.path as string);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
tracer.trace(`Error while connecting to device: ${error}`, { error });
|
|
191
|
+
return Promise.reject(error);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
transportInstance = new TransportNodeHidSingleton(HIDDevice, {
|
|
195
|
+
context,
|
|
188
196
|
});
|
|
189
|
-
}
|
|
190
197
|
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
const clearDeviceEventsListener = listenDevices(
|
|
199
|
+
() => {},
|
|
200
|
+
() => {
|
|
201
|
+
// Assumes any ledger disconnection concerns current transport
|
|
202
|
+
if (transportInstance) {
|
|
203
|
+
tracer.trace("Listened to on remove device event. Emitting a disconnect");
|
|
204
|
+
transportInstance.emit("disconnect");
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Disconnect event received from the transport instance.
|
|
211
|
+
*
|
|
212
|
+
* It could be after a disconnection coming from the HID library (e.g. device unplugged) or from the transport instance itself (e.g. close).
|
|
213
|
+
* Clearing the singleton instance.
|
|
214
|
+
* Currently, only 1 device at a time is supported.
|
|
215
|
+
*/
|
|
216
|
+
const onDisconnect = () => {
|
|
217
|
+
if (!transportInstance) {
|
|
218
|
+
tracer.trace("disconnect event without transport instance, ignoring ...");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.clearDisconnectAfterInactivityTimeout();
|
|
222
|
+
transportInstance.off("disconnect", onDisconnect);
|
|
223
|
+
transportInstance = null;
|
|
224
|
+
clearDeviceEventsListener();
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
transportInstance.on("disconnect", onDisconnect);
|
|
228
|
+
return Promise.resolve(transportInstance);
|
|
193
229
|
}
|
|
194
230
|
|
|
195
231
|
/**
|
|
196
|
-
*
|
|
232
|
+
* Exchanges with the device using APDU protocol
|
|
197
233
|
*
|
|
198
234
|
* @param apdu
|
|
199
235
|
* @returns a promise of apdu response
|
|
200
236
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const result = await super.exchange(apdu);
|
|
204
|
-
setDisconnectTimeout();
|
|
205
|
-
return result;
|
|
237
|
+
exchange(apdu: Buffer): Promise<Buffer> {
|
|
238
|
+
return super.exchange(apdu);
|
|
206
239
|
}
|
|
207
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Closes the transport instance by triggering a disconnection after some inactivity (no new `open`).
|
|
243
|
+
*
|
|
244
|
+
* Intentionally not disconnecting the device/closing the hid connection directly:
|
|
245
|
+
* The HID connection will only be closed after some inactivity.
|
|
246
|
+
*/
|
|
208
247
|
close(): Promise<void> {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
248
|
+
this.tracer.trace(
|
|
249
|
+
"Closing transport instance by triggering a disconnection after some inactivity",
|
|
250
|
+
{
|
|
251
|
+
hasInstance: !!transportInstance,
|
|
252
|
+
disconnectAfterInactivityTimeoutMs: DISCONNECT_AFTER_INACTIVITY_TIMEOUT_MS,
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
TransportNodeHidSingleton.setDisconnectAfterInactivityTimeout();
|
|
257
|
+
|
|
212
258
|
return Promise.resolve();
|
|
213
259
|
}
|
|
214
260
|
}
|