1 |
|
2 |
|
3 | declare var __VERSION__: string;
|
4 |
|
5 | import * as HID from 'node-hid';
|
6 |
|
7 | import type {HID as HIDDevice, HIDDeviceDescription} from 'node-hid';
|
8 |
|
9 | import {debugInOut} from '../debug-decorator';
|
10 |
|
11 | const REPORT_ID = 63;
|
12 |
|
13 | const TREZOR_DESC = {
|
14 | vendorId: 0x534c,
|
15 | productId: 0x0001,
|
16 | interface: 0,
|
17 | };
|
18 |
|
19 | type TrezorDeviceInfo = {path:string};
|
20 |
|
21 | export default class NodeHidPlugin {
|
22 | name: string = `NodeHidPlugin`;
|
23 |
|
24 | _sessionCounter: number = 0;
|
25 |
|
26 |
|
27 | _devices: {[path: string]: HIDDevice} = {};
|
28 |
|
29 | version: string = __VERSION__;
|
30 |
|
31 | debug: boolean = false;
|
32 |
|
33 | @debugInOut
|
34 | async init(debug: ?boolean): Promise<void> {
|
35 |
|
36 | if (typeof process === `object` && process != null && process.toString() === `[object process]`) {
|
37 | return;
|
38 | }
|
39 | HID.devices();
|
40 | throw new Error(`Not a node environment.`);
|
41 | }
|
42 |
|
43 | async enumerate(): Promise<Array<TrezorDeviceInfo>> {
|
44 | const devices = HID.devices()
|
45 | .filter(d =>
|
46 | d.vendorId === TREZOR_DESC.vendorId &&
|
47 | d.productId === TREZOR_DESC.productId &&
|
48 | d.interface === TREZOR_DESC.interface
|
49 | )
|
50 | .map((device: HIDDeviceDescription): TrezorDeviceInfo => {
|
51 | const path = device.path;
|
52 | return {
|
53 | path,
|
54 | };
|
55 | });
|
56 | return devices;
|
57 | }
|
58 |
|
59 | async send(path: string, session: string, data: ArrayBuffer): Promise<void> {
|
60 | const device = this._devices[path];
|
61 | const toWrite = [REPORT_ID].concat(Array.from(new Uint8Array(data)));
|
62 | device.write(toWrite);
|
63 | }
|
64 |
|
65 | receive(path: string, session: string): Promise<ArrayBuffer> {
|
66 | const device: HIDDevice = this._devices[path];
|
67 | return new Promise((resolve, reject) => {
|
68 | device.read((error, data) => {
|
69 | if (error != null) {
|
70 | reject(error);
|
71 | } else {
|
72 | if (data == null) {
|
73 | reject(new Error(`Data is null`));
|
74 | } else {
|
75 | if (data[0] !== 63) {
|
76 | reject(new Error(`Invalid data; first byte should be 63, is ${data[0]}`));
|
77 | }
|
78 | resolve(nodeBuffer2arrayBuffer(data.slice(1)));
|
79 | }
|
80 | }
|
81 | });
|
82 | });
|
83 | }
|
84 |
|
85 | @debugInOut
|
86 | async connect(path: string): Promise<string> {
|
87 | const counter = this._sessionCounter;
|
88 | this._sessionCounter++;
|
89 | const device: HIDDevice = new HID.HID(path);
|
90 | this._devices[path] = device;
|
91 |
|
92 | device.pause();
|
93 | return counter.toString();
|
94 | }
|
95 |
|
96 | @debugInOut
|
97 | async disconnect(path: string, session: string): Promise<void> {
|
98 | const device = this._devices[path];
|
99 | device.close();
|
100 | delete this._devices[path];
|
101 | }
|
102 | }
|
103 |
|
104 | function nodeBuffer2arrayBuffer(b: Buffer): ArrayBuffer {
|
105 | return new Uint8Array(b).buffer;
|
106 | }
|